• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2025-03-11 22:03 Aet 隐藏边栏 |   抢沙发  6 
文章评分 2 次,平均分 5.0

关于auto推导

  1. 关于t最后推导出的类型
    1. auto 的默认类型推导规则会 忽略引用,直接推导出值的类型
    2. 函数 test() 返回的是 std::string&(引用),但 auto t = test(); 会 拷贝引用指向的值,生成一个新的 std::string 对象

多态相关

概述

  1. C++ 的多态主要通过 虚函数和 动态绑定实现,其核心依赖于 虚函数表(vtable) 和 虚函数指针(vptr) 的机制

多态基本概念

  1. 多态允许通过 基类指针或引用 调用不同派生类对象的同名函数,实现 同一接口,多种行为

实现机制:虚函数表

  1. 虚函数表vtable的创建
    1. 每个类(包含虚函数或继承虚函数)会生成一个虚函数表(vtable
    2. vtable 结构:一个函数指针数组,存储该类的所有虚函数地址
      比如基类 Animalvtable:包含 Animal::speak() 的地址
      派生类 Dogvtable:覆盖为 Dog::speak() 的地址
  2. 虚函数指针vptr
    1. 每个对象 内部会隐式包含一个指向其类的 vtable 的指针(vptr
    2. 对象构造时,编译器自动设置 vptr 指向当前类的 vtable
    3. 派生类对象构造时,先调用基类构造函数(vptr 指向基类 vtable),再调用派生类构造函数(vptr 更新为派生类 vtable

动态绑定过程

  1. 当通过 基类指针/引用 调用虚函数时,实际执行以下步骤:
    1. 通过对象的 vptr 找到对应的 vtable
    2. vtable 中查找虚函数的地址(根据函数在表中的偏移量)
    3. 调用该地址对应的函数(可能是基类或派生类的实现)

问题

  1. vtable是编译器就确定好的?
    1. 虚函数表(vtable)的布局和内容是在编译阶段由编译器确定好的,但 虚函数指针(vptr)的指向是在运行时动态绑定 的
    2. 每个类只有一份虚函数表,所有该类的对象共享同一份 vtable
  2. 编译器在编译期生成派生类的虚函数表时,是先拷贝的基类的虚函数表,然后,派生类中有和基类同名函数,就把该同名函数的地址覆盖为派生类自己的同名函数的地址,如果有派生类自己的虚函数,就新添加到虚函数表里,这样吗?
    1. 是这样,构造流程如下:
    2. 编译器会先拷贝基类的虚函数表,作为派生类虚函数表的初始模板
      此时,派生类的虚函数表中所有虚函数地址与基类一致
    3. 如果派生类重写(override)了基类的某个虚函数,则将基类虚函数表中对应位置的函数地址替换为派生类的函数地址
      覆盖操作保持原函数在虚函数表中的顺序和偏移量不变
    4. 如果派生类定义了新的虚函数(基类中没有的虚函数),这些函数地址会被追加到虚函数表的末尾

多继承

A类多继承自B类和C类,假如B类和C类有同名函数

  1. 编译器无法确定调用哪个基类的版本,从而导致编译错误
  2. 解决方法一:显示指定作用域

  1. 解决方法二:在派生类中重写函数

  1. 解决方法三:使用虚继承
    1. 如果 BC 本身继承自同一个基类(菱形继承),可以通过 虚继承 消除歧义,但此方法 不适用于无关的基类

  1. 解决方法四:虚函数覆盖
    1. 如果 BC 的同名函数是 虚函数,且 A 重写(override)了该函数,则调用 A 的版本:

编码

编码介绍

  1. ascii
  2. 多字节,如gb2312big5
  3. Unicode
  4. utf-8

API默认编码

  1. 系统内核和API
    1. Windows 底层(如系统 API、内核)统一使用 UTF-16 Little Endian 编码,通过 wchar_t(宽字符)实现

智能指针

lambda相关及场景

  1. 本质

    1. Lambda 表达式本质上是一个匿名函数对象
  2. 使用场景如下:

  3. STL 算法的谓词

    1. 简化算法的参数传递,避免定义单独的函数或函数对象:

  1. 回调函数 callback
    1. 替代函数指针或 std::function,实现简洁的回调逻辑:

  1. 线程任务
    1. 作为线程入口函数,捕获局部变量:

  1. 闭包
    1. 保存上下文状态,实现有状态的函数对象:

  1. 事件处理
    1. GUI 或异步编程中处理事件:

  1. 延迟执行
    1. 将逻辑封装为 lambda,推迟到特定时机执行:

lambda实现机制

  1. 概述
    1. Lambda 表达式在编译器层面会被转换为一个匿名类(闭包类型),其核心机制如下:
  2. 匿名类的生成
    1. 编译器为每个 lambda 生成一个唯一的类,包含:
    2. 捕获的变量:作为类的成员变量
    3. 重载的 operator():实现 lambda 的函数体逻辑

  1. 变量捕获的方式
    1. 值捕获 ([x]):捕获变量的副本,生成类成员变量
    2. 引用捕获 ([&x]):捕获变量的引用,生成引用类型成员
    3. 隐式捕获 ([=]/[&]):自动捕获所有外部变量(值或引用)
  2. mutable
    1. 默认情况下,operator()const 的。若需修改值捕获的变量,需标记 mutable

  1. 类型推导
    1. lambda 体为 return expr;,返回类型自动推导为 expr 的类型
    2. 复杂逻辑需显式指定返回类型:

  1. 闭包对象的生命周期
    1. 值捕获的变量在闭包对象析构时自动释放
    2. 引用捕获的变量需确保其生命周期长于闭包对象

语言相关

  1. 总结就是,C++可以之间调用C语言API
  2. 但是C语言代码想调用C++API,需要C++APIextern "C"的方式导出

定位内存泄漏

代码审查

工具检测

  1. 静态分析工具

    1. Clang-Tidy:通过静态分析检查潜在的内存泄漏
    2. Cppcheck
  2. 动态分析工具

    1. ValgrindLinux/macOS):
    2. AddressSanitizer
    3. Dr. Memory(跨平台):

Clang-Tidy

cppcheck

ValgrindLinux/macOS

AddressSanitizerASan,跨平台):

  1. 编译时添加 -fsanitize=address

  1. 运行程序后,ASan 会报告泄漏位置和调用栈

Dr. Memory(跨平台)

单例相关

传统的双重检查锁定(C++11之前的实现)

  1. 非线程安全

  1. 代码解析
    1. instance = new Singleton(); 这一行,编译器和 CPU 可能重排序以下步骤:
    2. 分配内存
    3. 调用构造函数
    4. 将内存地址赋值给 instance
    5. 可能导致不同线程可能看到 instance 的不一致状态(由于缓存一致性未强制同步)

简单的线程安全单例(C++11及以上)

  1. 代码解析
    1. C++11 标准规定:局部静态变量的初始化在首次进入声明语句时由编译器保证线程安全
    2. 无需手动加锁,编译器会自动生成线程安全的初始化代码

对于C++11之前的那种传统的双重检查锁定,C++11及以后的线程安全实现

  1. 代码解析
    1. std::atomic<Singleton*> 确保对 instance 的读写是原子的
    2. std::memory_order_acquire:确保在读取 instance 时,后续操作不会被重排序到该读取之前
    3. std::memory_order_release:确保在写入 instance 时,之前的操作不会被重排序到该写入之后

使用std::call_once来实现

  1. 代码解析
    1. 优点:无需手动管理锁和原子操作,代码更简洁且线程安全
    2. 缺点:依赖标准库实现,但现代编译器中性能与双重检查锁定相当

本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0 最后编辑于:2025-03-13
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享