《深度探索C++对象模型》阅读笔记(完整版)

《深度探索C++对象模型》阅读笔记(完整版)

文章目录

  • 《深度探索C++对象模型》阅读笔记(完整版)
    • [1. 关于对象(Object Lessons)](#1. 关于对象(Object Lessons))
      • [1.1 C++对象模型(The C++ Object Model)](#1.1 C++对象模型(The C++ Object Model))
        • [1.1.1 语言中的对象模型](#1.1.1 语言中的对象模型)
        • [1.1.2 简单对象模型(A Simple Object Model)](#1.1.2 简单对象模型(A Simple Object Model))
        • [1.1.3 表格驱动对象模型(A Table-driven Object Model)](#1.1.3 表格驱动对象模型(A Table-driven Object Model))
        • [1.1.4 C++对象模型(The C++ Object Model)](#1.1.4 C++对象模型(The C++ Object Model))
      • [1.2 关键词的差异(A Keyword Distinction)](#1.2 关键词的差异(A Keyword Distinction))
        • [1.2.1 struct vs class](#1.2.1 struct vs class)
        • [1.2.2 策略性正确的struct](#1.2.2 策略性正确的struct)
      • [1.3 对象的差异(An Object Distinction)](#1.3 对象的差异(An Object Distinction))
        • [1.3.1 程序设计范型(Programming Paradigms)](#1.3.1 程序设计范型(Programming Paradigms))
        • [1.3.2 对象的内存需求](#1.3.2 对象的内存需求)
        • [1.3.3 多态的成本](#1.3.3 多态的成本)
      • [1.4 指针的类型(The Type of a Pointer)](#1.4 指针的类型(The Type of a Pointer))
        • [1.4.1 指针类型的意义](#1.4.1 指针类型的意义)
        • [1.4.2 转型(Cast)操作](#1.4.2 转型(Cast)操作)
    • [2. 构造函数语义学(The Semantics of Constructors)](#2. 构造函数语义学(The Semantics of Constructors))
      • [2.1 默认构造函数的构造操作(Default Constructor Construction)](#2.1 默认构造函数的构造操作(Default Constructor Construction))
        • [2.1.1 编译器何时生成默认构造函数](#2.1.1 编译器何时生成默认构造函数)
        • [2.1.2 被合成的默认构造函数的行为](#2.1.2 被合成的默认构造函数的行为)
      • [2.2 拷贝构造函数的构造操作(Copy Constructor Construction)](#2.2 拷贝构造函数的构造操作(Copy Constructor Construction))
        • [2.2.1 默认成员初始化(Default Memberwise Initialization)](#2.2.1 默认成员初始化(Default Memberwise Initialization))
        • [2.2.2 位逐次拷贝(Bitwise Copy Semantics)](#2.2.2 位逐次拷贝(Bitwise Copy Semantics))
        • [2.2.3 不能使用位逐次拷贝的情况](#2.2.3 不能使用位逐次拷贝的情况)
      • [2.3 程序转化语义(Program Transformation Semantics)](#2.3 程序转化语义(Program Transformation Semantics))
        • [2.3.1 显式的初始化操作(Explicit Initialization)](#2.3.1 显式的初始化操作(Explicit Initialization))
        • [2.3.2 参数的初始化(Argument Initialization)](#2.3.2 参数的初始化(Argument Initialization))
        • [2.3.3 返回值的初始化(Return Value Initialization)](#2.3.3 返回值的初始化(Return Value Initialization))
        • [2.3.4 Named Return Value (NRV) 优化](#2.3.4 Named Return Value (NRV) 优化)
      • [2.4 成员初始化列表(Member Initialization List)](#2.4 成员初始化列表(Member Initialization List))
        • [2.4.1 必须使用初始化列表的情况](#2.4.1 必须使用初始化列表的情况)
        • [2.4.2 初始化顺序](#2.4.2 初始化顺序)
        • [2.4.3 初始化列表的效率](#2.4.3 初始化列表的效率)
    • [3. Data语义学(The Semantics of Data)](#3. Data语义学(The Semantics of Data))
      • [3.1 Data Member的布局(The Binding of a Data Member)](#3.1 Data Member的布局(The Binding of a Data Member))
        • [3.1.1 数据成员的布局规则](#3.1.1 数据成员的布局规则)
        • [3.1.2 访问级别对布局的影响](#3.1.2 访问级别对布局的影响)
        • [3.1.3 边界对齐(Alignment)](#3.1.3 边界对齐(Alignment))
      • [3.2 Data Member的存取(Data Member Access)](#3.2 Data Member的存取(Data Member Access))
        • [3.2.1 静态数据成员(Static Data Members)](#3.2.1 静态数据成员(Static Data Members))
        • [3.2.2 非静态数据成员(Nonstatic Data Members)](#3.2.2 非静态数据成员(Nonstatic Data Members))
        • [3.2.3 通过指针访问](#3.2.3 通过指针访问)
      • [3.3 继承与Data Member(Inheritance and the Data Member)](#3.3 继承与Data Member(Inheritance and the Data Member))
        • [3.3.1 只有继承没有多态](#3.3.1 只有继承没有多态)
        • [3.3.2 加上多态](#3.3.2 加上多态)
        • [3.3.3 多重继承(Multiple Inheritance)](#3.3.3 多重继承(Multiple Inheritance))
        • [3.3.4 虚拟继承(Virtual Inheritance)](#3.3.4 虚拟继承(Virtual Inheritance))
      • [3.4 对象成员的效率(Object Member Efficiency)](#3.4 对象成员的效率(Object Member Efficiency))
        • [3.4.1 聚合(Aggregation)vs 继承](#3.4.1 聚合(Aggregation)vs 继承)
        • [3.4.2 不同继承模型的效率比较](#3.4.2 不同继承模型的效率比较)
      • [3.5 指向成员的指针(Pointer to Data Members)](#3.5 指向成员的指针(Pointer to Data Members))
    • [4. Function语义学(The Semantics of Function)](#4. Function语义学(The Semantics of Function))
      • [4.1 Member的各种调用方式(Varieties of Member Invocation)](#4.1 Member的各种调用方式(Varieties of Member Invocation))
        • [4.1.1 非静态成员函数(Nonstatic Member Functions)](#4.1.1 非静态成员函数(Nonstatic Member Functions))
        • [4.1.2 虚函数(Virtual Member Functions)](#4.1.2 虚函数(Virtual Member Functions))
        • [4.1.3 静态成员函数(Static Member Functions)](#4.1.3 静态成员函数(Static Member Functions))
      • [4.2 虚函数机制(Virtual Member Functions)](#4.2 虚函数机制(Virtual Member Functions))
        • [4.2.1 单一继承下的虚函数](#4.2.1 单一继承下的虚函数)
        • [4.2.2 多重继承下的虚函数](#4.2.2 多重继承下的虚函数)
        • [4.2.3 虚拟继承下的虚函数](#4.2.3 虚拟继承下的虚函数)
      • [4.3 函数的效能(Function Efficiency)](#4.3 函数的效能(Function Efficiency))
        • [4.3.1 各种函数调用的比较](#4.3.1 各种函数调用的比较)
      • [4.4 指向成员函数的指针(Pointer-to-Member Functions)](#4.4 指向成员函数的指针(Pointer-to-Member Functions))
        • [4.4.1 指向非虚成员函数的指针](#4.4.1 指向非虚成员函数的指针)
        • [4.4.2 指向虚函数的指针](#4.4.2 指向虚函数的指针)
        • [4.4.3 多重继承下的成员函数指针](#4.4.3 多重继承下的成员函数指针)
      • [4.5 内联函数(Inline Functions)](#4.5 内联函数(Inline Functions))
        • [4.5.1 内联函数的处理](#4.5.1 内联函数的处理)
        • [4.5.2 形式参数(Formal Arguments)](#4.5.2 形式参数(Formal Arguments))
        • [4.5.3 局部变量(Local Variables)](#4.5.3 局部变量(Local Variables))
    • [5. 构造、析构、拷贝语义学](#5. 构造、析构、拷贝语义学)
      • [5.1 无继承情况下的对象构造](#5.1 无继承情况下的对象构造)
        • [5.1.1 抽象数据类型(Abstract Data Type)](#5.1.1 抽象数据类型(Abstract Data Type))
        • [5.1.2 为继承做准备](#5.1.2 为继承做准备)
      • [5.2 继承体系下的对象构造](#5.2 继承体系下的对象构造)
        • [5.2.1 虚拟继承(Virtual Inheritance)](#5.2.1 虚拟继承(Virtual Inheritance))
        • [5.2.2 vptr初始化语义学(The Semantics of the vptr Initialization)](#5.2.2 vptr初始化语义学(The Semantics of the vptr Initialization))
      • [5.3 对象的拷贝语义学(Object Copy Semantics)](#5.3 对象的拷贝语义学(Object Copy Semantics))
        • [5.3.1 拷贝赋值操作符(Copy Assignment Operator)](#5.3.1 拷贝赋值操作符(Copy Assignment Operator))
        • [5.3.2 虚拟基类的拷贝赋值](#5.3.2 虚拟基类的拷贝赋值)
      • [5.4 对象的功能(Object Efficiency)](#5.4 对象的功能(Object Efficiency))
        • [5.4.1 析构语义学(Semantics of Destruction)](#5.4.1 析构语义学(Semantics of Destruction))
      • [5.5 全局对象(Global Objects)](#5.5 全局对象(Global Objects))
        • [5.5.1 静态初始化(Static Initialization)](#5.5.1 静态初始化(Static Initialization))
        • [5.5.2 局部静态对象(Local Static Objects)](#5.5.2 局部静态对象(Local Static Objects))
      • [5.6 对象数组(Array of Objects)](#5.6 对象数组(Array of Objects))
        • [5.6.1 数组的构造](#5.6.1 数组的构造)
        • [5.6.2 new和delete数组](#5.6.2 new和delete数组)
    • [6. 执行期语义学(Runtime Semantics)](#6. 执行期语义学(Runtime Semantics))
      • [6.1 对象的构造和析构(Object Construction and Destruction)](#6.1 对象的构造和析构(Object Construction and Destruction))
        • [6.1.1 全局对象的静态初始化](#6.1.1 全局对象的静态初始化)
        • [6.1.2 局部静态对象(Local Static Objects)](#6.1.2 局部静态对象(Local Static Objects))
      • [6.2 new和delete运算符(Operators new and delete)](#6.2 new和delete运算符(Operators new and delete))
        • [6.2.1 new运算符的实现](#6.2.1 new运算符的实现)
        • [6.2.2 数组的new](#6.2.2 数组的new)
        • [6.2.3 placement new](#6.2.3 placement new)
      • [6.3 临时对象(Temporary Objects)](#6.3 临时对象(Temporary Objects))
        • [6.3.1 临时对象的生命周期](#6.3.1 临时对象的生命周期)
        • [6.3.2 临时对象的优化](#6.3.2 临时对象的优化)
      • [6.4 对象的生命期(Object Lifetime)](#6.4 对象的生命期(Object Lifetime))
        • [6.4.1 对象生命期的概念](#6.4.1 对象生命期的概念)
        • [6.4.2 条件性构造](#6.4.2 条件性构造)
    • [7. 站在对象模型的尖端](#7. 站在对象模型的尖端)
      • [7.1 Template(模板)](#7.1 Template(模板))
        • [7.1.1 Template的实例化(Template Instantiation)](#7.1.1 Template的实例化(Template Instantiation))
        • [7.1.2 Template的错误报告](#7.1.2 Template的错误报告)
        • [7.1.3 Template的实例化策略](#7.1.3 Template的实例化策略)
      • [7.2 异常处理(Exception Handling)](#7.2 异常处理(Exception Handling))
        • [7.2.1 异常处理的对象模型](#7.2.1 异常处理的对象模型)
        • [7.2.2 异常处理的成本](#7.2.2 异常处理的成本)
        • [7.2.3 支持异常处理的对象构造](#7.2.3 支持异常处理的对象构造)
      • [7.3 执行期类型识别(Runtime Type Identification, RTTI)](#7.3 执行期类型识别(Runtime Type Identification, RTTI))
        • [7.3.1 RTTI的实现](#7.3.1 RTTI的实现)
        • [7.3.2 dynamic_cast的实现](#7.3.2 dynamic_cast的实现)
      • [7.4 效率有了,弹性呢?(Efficiency and Flexibility)](#7.4 效率有了,弹性呢?(Efficiency and Flexibility))
        • [7.4.1 动态共享库(Dynamic Shared Libraries)](#7.4.1 动态共享库(Dynamic Shared Libraries))
        • [7.4.2 共享内存(Shared Memory)](#7.4.2 共享内存(Shared Memory))

1. 关于对象(Object Lessons)

1.1 C++对象模型(The C++ Object Model)

1.1.1 语言中的对象模型

在C语言中,"数据"和"处理数据的操作"是分开声明的,语言本身并没有支持"数据和函数"之间的关联性。而C++通过抽象数据类型(ADT)将数据和操作封装在一起。

cpp 复制代码
// C语言风格
typedef struct point3d {
    float x;
    float y; 
    float z;
} Point3d;

void Point3d_print(const Point3d *pd) {
    printf("(%f, %f, %f)", pd->x, pd->y, pd->z);
}

// C++风格
class Point3d {
private:
    float x, y, z;
public:
    Point3d(float xx = 0.0, float yy = 0.0, float zz = 0.0)
        : x(xx), y(yy), z(zz) { }
    
    void print() const {
        printf("(%f, %f, %f)", x, y, z);
    }
};
1.1.2 简单对象模型(A Simple Object Model)

在简单对象模型中,一个对象是一系列的槽(slots),每个槽指向一个成员。成员按声明顺序排列,包括数据成员和函数成员。

cpp 复制代码
// 概念模型
class Point {
    float x;
    float y;
    void print();
};

// 简单对象模型的内存布局
// Point object:
// +--------+
// | slot1  |---> x (float)
// +--------+
// | slot2  |---> y (float)  
// +--------+
// | slot3  |---> print (function)
// +--------+

这个模型的特点:

  • 对象大小固定:slots数量 × 指针大小
  • 所有成员访问都是间接的(通过指针)
  • 避免了成员类型不同导致的存储问题
1.1.3 表格驱动对象模型(A Table-driven Object Model)

这个模型把所有与成员相关的信息抽出来,放在一个数据成员表和一个函数成员表中,对象本身只含有指向这两个表的指针。

cpp 复制代码
// 概念示意
class Point {
    float x, y;
    void print();
    float magnitude();
};

// 表格驱动模型
// Point object:
// +------------------+
// | data_table_ptr   |---> [offset_x][offset_y]
// +------------------+
// | func_table_ptr   |---> [&print][&magnitude]
// +------------------+

这个模型是虚函数表(virtual table)概念的起源。

1.1.4 C++对象模型(The C++ Object Model)

Stroustrup设计的C++对象模型从简单对象模型派生而来,对内存和存取时间做了优化:

cpp 复制代码
class Point3d {
    float x, y, z;
    static int count;
    
    void print();
    static int getCount();
    virtual void draw();
    virtual ~Point3d();
};

// 实际的C++对象模型布局
// Point3d object:
// +---------+
// | vptr    |---> vtbl[0] = &Point3d::draw
// +---------+     vtbl[1] = &Point3d::~Point3d
// | x       |
// +---------+
// | y       |
// +---------+
// | z       |
// +---------+
//
// 静态数据成员和所有函数都在对象之外

核心特征:

  1. 非静态数据成员被配置在每一个类对象内

  2. 静态数据成员被存放在所有类对象之外

  3. 静态和非静态函数成员被放在所有类对象之外

  4. 虚函数

    通过两个步骤支持:

    • 每个类产生一个虚函数表(vtbl)
    • 每个类对象添加一个指针(vptr),指向相关的虚函数表

1.2 关键词的差异(A Keyword Distinction)

1.2.1 struct vs class

在C++中,struct和class的唯一差别是默认访问级别:

  • struct默认是public
  • class默认是private
cpp 复制代码
struct S {
    int x;      // 默认public
    void f();   // 默认public
};

class C {
    int x;      // 默认private
public:
    void f();   // 明确声明public
};
1.2.2 策略性正确的struct

什么时候应该使用struct:

  1. 当你需要与C兼容的数据布局时
  2. 当所有数据都是public且没有函数时(POD - Plain Old Data)
  3. 当你需要明确的内存布局控制时
cpp 复制代码
// C兼容的struct
struct CCompatible {
    int id;
    char name[50];
    float value;
};

// 可以安全地在C和C++之间传递
extern "C" void process_data(CCompatible* data);

1.3 对象的差异(An Object Distinction)

1.3.1 程序设计范型(Programming Paradigms)

C++支持三种程序设计范型:

  1. 程序型(Procedural):C风格
  2. 抽象数据类型(ADT):封装
  3. 面向对象(Object-Oriented):继承和多态
cpp 复制代码
// 1. 程序型模型
char* strcpy(char* dest, const char* src);

// 2. ADT模型
class String {
    char* data;
public:
    String(const char* str);
    String& operator=(const String& rhs);
};

// 3. 面向对象模型
class Shape {
public:
    virtual void draw() = 0;
    virtual ~Shape() {}
};

class Circle : public Shape {
public:
    void draw() override;
};
1.3.2 对象的内存需求

对象的内存大小包括:

  1. 非静态数据成员的总和
  2. 由于对齐(alignment)而填补的空间
  3. 为了支持虚函数而产生的额外负担(vptr)
cpp 复制代码
class A {
    char c;     // 1 byte
    int i;      // 4 bytes
    // 3 bytes padding between c and i
};  // sizeof(A) = 8 (on typical 32-bit system)

class B {
    int i;      // 4 bytes  
    char c;     // 1 byte
    // 3 bytes padding at end
};  // sizeof(B) = 8

class C : public A {
    char c2;    // 1 byte
    // 3 bytes padding
};  // sizeof(C) = 12
1.3.3 多态的成本
cpp 复制代码
class ZooAnimal {
public:
    virtual void rotate() { }
    virtual ~ZooAnimal() { }
protected:
    int loc_x, loc_y;
};

class Bear : public ZooAnimal {
public:
    void rotate() override { }
    ~Bear() { }
protected:
    int cell_block;
};

// 内存布局
// Bear object:
// +-------------+ <-- ZooAnimal部分开始
// | vptr        | 
// +-------------+
// | loc_x       |
// +-------------+
// | loc_y       |
// +-------------+ <-- Bear部分开始
// | cell_block  |
// +-------------+

多态的主要成本:

  1. 每个对象增加一个vptr(通常4或8字节)
  2. 每个类增加一个vtbl
  3. 虚函数调用的间接性(通过vtbl)

1.4 指针的类型(The Type of a Pointer)

cpp 复制代码
class ZooAnimal { 
public:
    virtual void rotate();
    int loc_x, loc_y;
};

class Bear : public ZooAnimal { 
public:
    void rotate() override;
    int cell_block;
    
    void dance();  // Bear特有的函数
};

Bear b("Yogi");
Bear *pb = &b;
ZooAnimal *pz = &b;

// 不同指针类型的差异
pb->dance();    // OK: Bear指针可以调用Bear的函数
// pz->dance(); // 错误: ZooAnimal指针看不到dance()

// 但是虚函数调用是多态的
pz->rotate();   // 调用Bear::rotate()
pb->rotate();   // 同样调用Bear::rotate()
1.4.1 指针类型的意义

指针的类型决定了:

  1. 编译时决议:指针可以访问的接口
  2. 运行时决议:虚函数的实际调用
cpp 复制代码
// 指针的内存布局理解
void comparePointers() {
    Bear b;
    Bear *pb = &b;
    ZooAnimal *pz = &b;
    void *pv = &b;
    
    // 三个指针的值相同(都指向对象起始地址)
    assert((void*)pb == (void*)pz);
    assert((void*)pb == pv);
    
    // 但类型信息不同,影响可访问的成员
}
1.4.2 转型(Cast)操作
cpp 复制代码
// 向上转型(安全)
Bear b;
ZooAnimal *pz = &b;  // 隐式转换,总是安全的

// 向下转型(需要运行时检查)
ZooAnimal *pz = new Bear;
Bear *pb = dynamic_cast<Bear*>(pz);  // 运行时检查
if (pb) {
    pb->dance();  // 安全
}

// static_cast(编译时,不检查)
Bear *pb2 = static_cast<Bear*>(pz);  // 程序员保证正确性

2. 构造函数语义学(The Semantics of Constructors)

2.1 默认构造函数的构造操作(Default Constructor Construction)

2.1.1 编译器何时生成默认构造函数

C++新手常见的误解:"如果没有定义默认构造函数,编译器会自动生成一个"。这是错误的!

编译器只在以下四种情况下才会生成默认构造函数:

情况1:成员对象带有默认构造函数

cpp 复制代码
class Foo {
public:
    Foo() { cout << "Foo::Foo()" << endl; }
};

class Bar {
    Foo foo;    // Foo有默认构造函数
    char *str;  // 内置类型,不会被初始化
    
    // 编译器生成的默认构造函数类似于:
    // Bar() { 
    //     foo.Foo::Foo();  // 调用Foo的默认构造函数
    //     // str不会被初始化!
    // }
};

情况2:基类带有默认构造函数

cpp 复制代码
class Base {
public:
    Base() { x = 0; }
private:
    int x;
};

class Derived : public Base {
    int y;  // 不会被初始化
    
    // 编译器生成的默认构造函数:
    // Derived() {
    //     Base::Base();  // 调用基类构造函数
    //     // y不会被初始化
    // }
};

情况3:带有虚函数的类

cpp 复制代码
class Widget {
public:
    virtual void flip() = 0;
    // 编译器生成的默认构造函数会设置vptr
    // Widget() {
    //     __vptr = &Widget::__vtbl;
    // }
};

class Gadget : public Widget {
public:
    void flip() override { }
    // Gadget() {
    //     Widget::Widget();  // 先调用基类构造
    //     __vptr = &Gadget::__vtbl;  // 设置自己的vptr
    // }
};

情况4:带有虚基类的类

cpp 复制代码
class X { public: int i; };
class A : public virtual X { };
class B : public virtual X { };
class C : public A, public B { };

// C的构造函数必须调用虚基类X的构造函数
// 编译器会生成必要的代码来定位虚基类
2.1.2 被合成的默认构造函数的行为

重要概念:编译器合成的默认构造函数只满足编译器的需要,而不是程序的需要

cpp 复制代码
class Point {
    float x, y, z;
};

// Point没有默认构造函数!
// 因为编译器不需要为Point做任何事情
// x, y, z不会被初始化为0

void test() {
    Point p;  // p.x, p.y, p.z包含垃圾值
}

// 如果需要初始化,必须自己提供
class Point2 {
    float x, y, z;
public:
    Point2() : x(0), y(0), z(0) { }  // 显式初始化
};

2.2 拷贝构造函数的构造操作(Copy Constructor Construction)

2.2.1 默认成员初始化(Default Memberwise Initialization)

当没有提供显式的拷贝构造函数时,编译器会进行默认成员初始化:

cpp 复制代码
class String {
    char *str;
    int len;
};

String s1("Hello");
String s2 = s1;  // 位逐次拷贝(Bitwise Copy)

// s2.str == s1.str  (浅拷贝!两个指针指向同一内存)
// s2.len == s1.len
2.2.2 位逐次拷贝(Bitwise Copy Semantics)

如果一个类没有定义拷贝构造函数,编译器会分析是否可以使用位逐次拷贝:

cpp 复制代码
// 可以使用位逐次拷贝的情况
class Point3d {
    float x, y, z;  // POD类型
};

// 不能使用位逐次拷贝的情况
class String {
    char *str;
public:
    String(const char *s) {
        str = new char[strlen(s) + 1];
        strcpy(str, s);
    }
    // 需要深拷贝!
    String(const String& rhs) {
        str = new char[strlen(rhs.str) + 1];
        strcpy(str, rhs.str);
    }
    ~String() { delete[] str; }
};
2.2.3 不能使用位逐次拷贝的情况

编译器必须生成拷贝构造函数的四种情况:

情况1:类包含有拷贝构造函数的成员对象

cpp 复制代码
class Word {
    String str;  // String有拷贝构造函数
    int occurs;
    
    // 编译器生成:
    // Word(const Word& w) 
    //     : str(w.str),      // 调用String拷贝构造
    //       occurs(w.occurs) // 位拷贝
    // { }
};

情况2:类继承自有拷贝构造函数的基类

cpp 复制代码
class TextWord : public Word {
    Text *text;
    
    // 编译器生成:
    // TextWord(const TextWord& tw)
    //     : Word(tw),        // 调用基类拷贝构造
    //       text(tw.text)    // 位拷贝指针
    // { }
};

情况3:类声明了虚函数

cpp 复制代码
class Base {
    int data;
public:
    virtual void foo() { }
};

class Derived : public Base {
    int moreData;
public:
    void foo() override { }
};

void slicing() {
    Derived d;
    Base b = d;  // 对象切割,但vptr必须正确设置
    
    // b.__vptr必须指向Base::vtbl,不是Derived::vtbl
}

情况4:类派生自继承链中有虚基类

cpp 复制代码
class ZooAnimal {
    int x;
};

class Raccoon : virtual public ZooAnimal {
    int y;
};

class RedPanda : virtual public ZooAnimal {
    int z;
};

class Panda : public Raccoon, public RedPanda {
    // 拷贝构造函数必须正确处理虚基类的位置
};

2.3 程序转化语义(Program Transformation Semantics)

2.3.1 显式的初始化操作(Explicit Initialization)

编译器如何转化初始化操作:

cpp 复制代码
// 原始代码
X x0;
void foo() {
    X x1(x0);      // 直接初始化
    X x2 = x0;     // 拷贝初始化
    X x3 = X(x0);  // 显式临时对象
}

// 可能的转化(伪代码)
void foo() {
    // x1的定义被重写
    X x1;
    x1.X::X(x0);   // 拷贝构造函数作为普通函数调用
    
    // x2的定义被重写  
    X x2;
    x2.X::X(x0);
    
    // x3的定义被重写
    X x3;
    x3.X::X(x0);
}
2.3.2 参数的初始化(Argument Initialization)
cpp 复制代码
void foo(X x0);

X xx;
foo(xx);

// 编译器转化为:
// 1. 创建临时对象
X __temp0;
__temp0.X::X(xx);  // 调用拷贝构造

// 2. 调用函数
foo(__temp0);

// 3. 析构临时对象  
__temp0.X::~X();
2.3.3 返回值的初始化(Return Value Initialization)
cpp 复制代码
X bar() {
    X xx;
    // 处理xx
    return xx;
}

// 编译器转化(两阶段):
// 1. 加入额外参数
void bar(X& __result) {  // 返回值通过引用传递
    X xx;
    // 处理xx
    
    // 2. 拷贝构造到返回值
    __result.X::X(xx);
    
    // 3. 析构局部对象
    xx.X::~X();
    return;
}

// 调用端转化
X x = bar();
// 变成:
X x;  // 不初始化
bar(x);  // x作为隐藏参数传入
2.3.4 Named Return Value (NRV) 优化
cpp 复制代码
X bar() {
    X xx;
    // 对xx进行操作
    return xx;
}

// 启用NRV优化后:
void bar(X& __result) {
    // 直接在__result的空间构造,避免拷贝
    __result.X::X();  // 默认构造
    
    // 所有对xx的操作都作用于__result
}

2.4 成员初始化列表(Member Initialization List)

2.4.1 必须使用初始化列表的情况

以下四种情况必须使用成员初始化列表:

cpp 复制代码
class Example {
    const int ci;        // 1. const成员
    int& ri;             // 2. 引用成员  
    Base base;           // 3. 没有默认构造函数的基类
    NoDefault obj;       // 4. 没有默认构造函数的成员对象
    
public:
    Example(int i, int& r, int b, int o) 
        : ci(i),         // 必须在初始化列表中
          ri(r),         // 必须在初始化列表中
          base(b),       // 必须在初始化列表中
          obj(o)         // 必须在初始化列表中
    {
        // 构造函数体
    }
};
2.4.2 初始化顺序

成员初始化的顺序是由声明顺序决定的,而不是初始化列表的顺序:

cpp 复制代码
class X {
    int i;
    int j;
public:
    X(int val) : j(val), i(j) { }  // 危险!i先于j初始化
    // i会用未初始化的j来初始化
};

// 正确的做法
class X {
    int i;
    int j;
public:
    X(int val) : i(val), j(i) { }  // OK,按声明顺序
};
2.4.3 初始化列表的效率
cpp 复制代码
class Word {
    String name;
    int count;
    
public:
    // 低效版本
    Word() {
        name = "default";  // 先默认构造,再赋值
        count = 0;
    }
    
    // 高效版本  
    Word() : name("default"), count(0) {
        // 直接构造,避免额外的默认构造
    }
};

编译器对初始化列表的扩展:

cpp 复制代码
// 原始代码
X::X(int i, int j) : base(i), mem1(j), mem2(0) {
    // 用户代码
}

// 编译器扩展后
X::X(int i, int j) {
    // 1. 调用基类构造函数
    base::base(i);
    
    // 2. 按声明顺序初始化成员
    mem1.Type1::Type1(j);
    mem2.Type2::Type2(0);
    
    // 3. 执行用户代码
}

3. Data语义学(The Semantics of Data)

3.1 Data Member的布局(The Binding of a Data Member)

3.1.1 数据成员的布局规则

C++标准保证:

  1. 同一访问级别中,成员的排列顺序与声明顺序一致
  2. 后声明的成员在对象中有较高的地址
cpp 复制代码
class Point3d {
    float x;
    static float origin;  // 静态成员不占对象空间
    float y;
    static int count;
    float z;
};

// 对象布局(静态成员不在对象中):
// +--------+
// |   x    | offset 0
// +--------+
// |   y    | offset 4  
// +--------+
// |   z    | offset 8
// +--------+
// sizeof(Point3d) = 12
3.1.2 访问级别对布局的影响

不同的访问级别(public/protected/private)之间的相对顺序是未定义的:

cpp 复制代码
class Complex {
public:
    double real;    // 第一个访问块
    
private:
    double imag;    // 第二个访问块
    
public:
    int id;         // 第三个访问块
};

// 可能的布局1:按声明顺序
// real -> imag -> id

// 可能的布局2:合并public块  
// real -> id -> imag
3.1.3 边界对齐(Alignment)
cpp 复制代码
class Aligned {
    char c1;    // 1 byte
    // 3 bytes padding (假设int需要4字节对齐)
    int i;      // 4 bytes
    char c2;    // 1 byte  
    // 3 bytes padding
};  // sizeof = 12

// 优化布局
class Optimized {
    int i;      // 4 bytes
    char c1;    // 1 byte
    char c2;    // 1 byte
    // 2 bytes padding
};  // sizeof = 8

3.2 Data Member的存取(Data Member Access)

3.2.1 静态数据成员(Static Data Members)

静态数据成员存储在程序的数据段,而不是类对象中:

cpp 复制代码
class Point3d {
    static int count;  // 声明
    float x, y, z;
};

int Point3d::count = 0;  // 定义,分配存储

// 存取静态成员
Point3d::count++;        // 通过类名
Point3d p;
p.count++;              // 通过对象(但不推荐)

// 编译器处理
// &Point3d::count 得到的是实际地址
// 不需要对象就可以访问
3.2.2 非静态数据成员(Nonstatic Data Members)

非静态数据成员的存取需要通过对象的起始地址加上偏移量:

cpp 复制代码
Point3d origin;
origin.x = 0.0;

// 编译器转化为:
&origin + (&Point3d::x - 1);  // -1是为了区分空指针

// 成员指针的表示
float Point3d::*pm = &Point3d::x;
// pm实际存储的是x在Point3d中的偏移量+1
3.2.3 通过指针访问
cpp 复制代码
Point3d *pt = new Point3d;
pt->x = 0.0;

// 如果x是第一个成员,偏移量为0
// &(pt->x) = pt + 0

// 考虑继承的情况
class Point2d {
    float x, y;
};

class Point3d : public Point2d {
    float z;
};

Point3d *p3d = new Point3d;
p3d->z = 0.0;
// &(p3d->z) = p3d + sizeof(Point2d)

3.3 继承与Data Member(Inheritance and the Data Member)

3.3.1 只有继承没有多态
cpp 复制代码
// 没有虚函数的继承
class Point2d {
    float x, y;
};

class Point3d : public Point2d {
    float z;
};

// Point3d对象布局:
// +-----------+ 
// | x (继承)  | offset 0
// +-----------+
// | y (继承)  | offset 4
// +-----------+
// | z (自己)  | offset 8  
// +-----------+
// sizeof(Point3d) = 12

这种情况下,基类子对象在派生类中保持原样,效率与C struct一样。

3.3.2 加上多态
cpp 复制代码
class Point2d {
public:
    virtual ~Point2d();
    virtual void print();
    float x, y;
};

class Point3d : public Point2d {
public:
    ~Point3d();
    void print() override;
    float z;
};

// Point3d对象布局(典型实现):
// +-------------+
// | vptr        | offset 0 (指向Point3d的vtbl)
// +-------------+
// | x           | offset 4/8 (取决于指针大小)
// +-------------+
// | y           |
// +-------------+
// | z           |
// +-------------+

多态引入的额外成本:

  1. 每个对象增加一个vptr
  2. 每个类需要一个vtbl
  3. 构造函数需要设置vptr
  4. 析构函数需要通过虚函数机制调用
3.3.3 多重继承(Multiple Inheritance)
cpp 复制代码
class Point2d {
    float x, y;
};

class Vertex {
    Vertex *next;
    
public:
    virtual void print();
};

class Point3d : public Point2d, public Vertex {
    float z;
    
public:
    void print() override;
};

// Point3d对象布局:
// +-------------+ <-- Point2d子对象
// | x           |
// +-------------+
// | y           |
// +-------------+ <-- Vertex子对象
// | vptr        | (Vertex的虚函数表指针)
// +-------------+
// | next        |
// +-------------+ <-- Point3d部分
// | z           |
// +-------------+

多重继承的复杂性:

cpp 复制代码
Point3d p3d;
Vertex *pv = &p3d;  // 需要调整指针!

// pv指向Vertex子对象的起始位置
// 不是Point3d对象的起始位置
// pv = (Vertex*)((char*)&p3d + delta)
// 其中delta = offsetof(Point3d, Vertex)
3.3.4 虚拟继承(Virtual Inheritance)

虚拟继承解决菱形继承问题:

cpp 复制代码
class ios { };
class istream : virtual public ios { };
class ostream : virtual public ios { };
class iostream : public istream, public ostream { };

// 没有虚拟继承,iostream会有两份ios
// 使用虚拟继承,只有一份ios

虚拟继承的实现模型:

cpp 复制代码
class Point2d {
    float x, y;
};

class Vertex : virtual public Point2d {
    Vertex *next;
};

class Point3d : virtual public Point2d {
    float z;
};

class Vertex3d : public Vertex, public Point3d {
    float mumble;
};

// Vertex3d对象可能的布局:
// +------------------+ <-- Vertex部分
// | __vbptr          | (虚基类表指针)
// +------------------+
// | next             |
// +------------------+ <-- Point3d部分
// | __vbptr          | (虚基类表指针)
// +------------------+
// | z                |
// +------------------+ <-- Vertex3d部分
// | mumble           |
// +------------------+ <-- Point2d部分(共享)
// | x                |
// +------------------+
// | y                |
// +------------------+

3.4 对象成员的效率(Object Member Efficiency)

3.4.1 聚合(Aggregation)vs 继承
cpp 复制代码
// 聚合方式
class Point2d {
    float x, y;
};

class Point3d {
    Point2d point2d;  // 包含
    float z;
};

// 继承方式
class Point3d : public Point2d {
    float z;
};

// 两种方式的存取效率相同
// 但继承支持多态,聚合不支持
3.4.2 不同继承模型的效率比较
cpp 复制代码
// 测试代码
void test_efficiency() {
    // 1. 独立类
    Point3d_independent p1;
    p1.x = 1.0;  // 直接存取
    
    // 2. 单一继承
    Point3d_single p2;
    p2.x = 1.0;  // 同样是直接存取
    
    // 3. 多重继承
    Point3d_multiple p3;
    p3.x = 1.0;  // 可能需要调整this指针
    
    // 4. 虚拟继承  
    Point3d_virtual p4;
    p4.x = 1.0;  // 需要间接存取
}

效率排序(从高到低):

  1. 独立类 ≈ 单一继承(无虚函数)
  2. 单一继承(有虚函数)
  3. 多重继承
  4. 虚拟继承

3.5 指向成员的指针(Pointer to Data Members)

cpp 复制代码
class Point3d {
public:
    float x, y, z;
};

// 定义指向成员的指针
float Point3d::*p1 = &Point3d::x;
float Point3d::*p2 = &Point3d::y;

// 使用
Point3d obj;
obj.*p1 = 1.0;  // 设置x

Point3d *ptr = &obj;
ptr->*p2 = 2.0;  // 设置y

// 实现细节
// &Point3d::x 返回x的偏移量+1
// 加1是为了区分空指针

在继承体系中的复杂性:

cpp 复制代码
class Base1 { int val1; };
class Base2 { int val2; };
class Derived : public Base1, public Base2 { int val3; };

int Base2::*bmp = &Base2::val2;
int Derived::*dmp = bmp;  // 需要调整偏移量

Derived d;
d.*dmp = 99;  // 正确设置Base2::val2

4. Function语义学(The Semantics of Function)

4.1 Member的各种调用方式(Varieties of Member Invocation)

4.1.1 非静态成员函数(Nonstatic Member Functions)

C++的设计准则之一:非静态成员函数至少必须和非成员函数有相同的效率。这是通过将成员函数转化为非成员函数实现的:

cpp 复制代码
// 原始的成员函数
class Point3d {
    float x, y, z;
public:
    float magnitude() const {
        return sqrt(x*x + y*y + z*z);
    }
};

// 编译器的内部转化
// 1. 改写函数原型,添加this指针
extern float magnitude__7Point3dFv(const Point3d *this);

// 2. 对成员的存取通过this指针
float magnitude__7Point3dFv(const Point3d *this) {
    return sqrt(this->x * this->x + 
                this->y * this->y + 
                this->z * this->z);
}

// 3. 调用的转化
Point3d obj;
obj.magnitude();
// 变成:
magnitude__7Point3dFv(&obj);

名称修饰(Name Mangling):

cpp 复制代码
class Point {
public:
    void x(float newX);
    float x();
};

// 可能的名称修饰:
// x__5PointFf     (void x(float))
// x__5PointFv     (float x())
4.1.2 虚函数(Virtual Member Functions)

虚函数的调用通过虚函数表进行:

cpp 复制代码
class Point {
public:
    virtual ~Point();
    virtual Point& mult(float) = 0;
    float x() const { return _x; }
    virtual float y() const { return 0; }
    virtual float z() const { return 0; }
    
protected:
    Point(float x = 0.0);
    float _x;
};

class Point2d : public Point {
public:
    Point2d(float x = 0.0, float y = 0.0) 
        : Point(x), _y(y) {}
    ~Point2d();
    
    Point2d& mult(float) override;
    float y() const override { return _y; }
    
protected:
    float _y;
};

// Point的虚函数表
Point::vtbl[0] = &Point::~Point
Point::vtbl[1] = &pure_virtual_called  // mult是纯虚函数
Point::vtbl[2] = &Point::y
Point::vtbl[3] = &Point::z

// Point2d的虚函数表
Point2d::vtbl[0] = &Point2d::~Point2d
Point2d::vtbl[1] = &Point2d::mult
Point2d::vtbl[2] = &Point2d::y
Point2d::vtbl[3] = &Point::z  // 继承Point的实现

虚函数调用的转化:

cpp 复制代码
Point *ptr = new Point2d;
ptr->mult(2.0);

// 编译器转化为:
(*ptr->vptr[1])(ptr, 2.0);
// vptr[1]指向Point2d::mult
4.1.3 静态成员函数(Static Member Functions)

静态成员函数的特点:

  1. 没有this指针
  2. 不能直接存取非静态成员
  3. 不能声明为const、volatile或virtual
  4. 不需要通过对象调用
cpp 复制代码
class Point3d {
    static int count;
public:
    static int object_count() { return count; }
};

// 编译器转化(几乎没有转化)
int object_count__5Point3dSFv() {
    return Point3d::count;
}

// 调用
int cnt = Point3d::object_count();
// 转化为:
int cnt = object_count__5Point3dSFv();

// 取地址
int (*ptr)() = &Point3d::object_count;
// 得到的是普通函数指针,不是成员函数指针

4.2 虚函数机制(Virtual Member Functions)

4.2.1 单一继承下的虚函数
cpp 复制代码
class Base {
public:
    virtual void f() { cout << "Base::f" << endl; }
    virtual void g() { cout << "Base::g" << endl; }
    virtual void h() { cout << "Base::h" << endl; }
};

class Derived : public Base {
public:
    void f() override { cout << "Derived::f" << endl; }
    virtual void g1() { cout << "Derived::g1" << endl; }
};

// 虚函数表布局
// Base的vtbl:
// [0] -> Base::f
// [1] -> Base::g  
// [2] -> Base::h

// Derived的vtbl:
// [0] -> Derived::f  (覆盖)
// [1] -> Base::g     (继承)
// [2] -> Base::h     (继承)
// [3] -> Derived::g1 (新增)

虚函数调用的成本分析:

cpp 复制代码
// 通过对象调用(编译时可确定)
Derived d;
d.f();  // 可能被优化为直接调用

// 通过指针调用(运行时决定)
Base *pb = &d;
pb->f();  // 必须通过虚函数表

// 虚函数调用的步骤:
// 1. 获取vptr: pb->vptr
// 2. 获取函数地址: pb->vptr[0]
// 3. 调用函数: (*pb->vptr[0])(pb)
4.2.2 多重继承下的虚函数

多重继承需要多个虚函数表:

cpp 复制代码
class Base1 {
public:
    virtual void f() { }
    virtual void g() { }
    virtual ~Base1() { }
};

class Base2 {
public:
    virtual void f() { }
    virtual void h() { }
    virtual ~Base2() { }
};

class Derived : public Base1, public Base2 {
public:
    void f() override { }  // 覆盖两个基类的f()
    virtual void g1() { }
};

// Derived对象布局:
// +----------+ <--- Derived对象开始
// | vptr1    | ---> Derived的主要vtbl (对应Base1)
// +----------+
// | Base1数据 |
// +----------+ <--- Base2子对象开始
// | vptr2    | ---> Derived的次要vtbl (对应Base2)
// +----------+
// | Base2数据 |
// +----------+
// | Derived数据|
// +----------+

this指针调整(thunk):

cpp 复制代码
Base2 *pb2 = new Derived;
pb2->f();  // 需要调整this指针

// 编译器可能生成thunk函数:
void Derived_f_thunk(Base2 *this) {
    Derived::f((Derived*)((char*)this - delta));
}
// delta是Base2子对象在Derived中的偏移量
4.2.3 虚拟继承下的虚函数

虚拟继承使虚函数机制更加复杂:

cpp 复制代码
class Point2d {
public:
    virtual void print() { }
    float x, y;
};

class Vertex : virtual public Point2d {
public:
    void print() override { }
    Vertex *next;
};

// 在虚拟继承下,需要在运行时确定虚基类的位置
// 这可能需要额外的间接层

4.3 函数的效能(Function Efficiency)

4.3.1 各种函数调用的比较
cpp 复制代码
// 测试类
class Test {
    int data;
public:
    void nonvirtual() { data = 1; }
    virtual void virtual_func() { data = 2; }
    static void static_func() { }
};

// 性能测试
void performance_test() {
    Test t;
    Test *pt = &t;
    
    // 1. 内联函数(最快)
    t.nonvirtual();  // 可能被内联
    
    // 2. 非虚成员函数
    pt->nonvirtual();  // 直接调用
    
    // 3. 虚函数(通过对象)
    t.virtual_func();  // 可能优化为直接调用
    
    // 4. 虚函数(通过指针)
    pt->virtual_func();  // 必须通过vtbl
    
    // 5. 静态成员函数
    Test::static_func();  // 普通函数调用
}

性能排序(从快到慢):

  1. 内联函数
  2. 静态成员函数 ≈ 非成员函数
  3. 非虚成员函数
  4. 单一继承的虚函数
  5. 多重继承的虚函数(需要调整this)
  6. 虚拟继承的虚函数

4.4 指向成员函数的指针(Pointer-to-Member Functions)

4.4.1 指向非虚成员函数的指针
cpp 复制代码
class Point3d {
public:
    float magnitude() const;
    Point3d& normalize();
};

// 声明指针
float (Point3d::*pmf)() const = &Point3d::magnitude;

// 使用指针
Point3d p;
float mag = (p.*pmf)();

Point3d *pp = &p;
float mag2 = (pp->*pmf)();

// 编译器的实现
// pmf实际存储的是函数地址
4.4.2 指向虚函数的指针
cpp 复制代码
class Base {
public:
    virtual void vfunc() { }
    void nonvfunc() { }
};

class Derived : public Base {
public:
    void vfunc() override { }
};

// 指向虚函数的指针
void (Base::*pvf)() = &Base::vfunc;

Base *pb = new Derived;
(pb->*pvf)();  // 调用Derived::vfunc

// 实现方式:pvf可能存储的是虚函数表索引
// 而不是实际的函数地址
4.4.3 多重继承下的成员函数指针
cpp 复制代码
class Base1 {
public:
    virtual void f() { }
};

class Base2 {
public:
    virtual void g() { }
};

class Derived : public Base1, public Base2 {
public:
    void f() override { }
    void g() override { }
};

// 成员函数指针在多重继承下需要额外信息
void (Base2::*pmf)() = &Base2::g;

Derived d;
(d.*pmf)();  // 需要调整this指针到Base2子对象

// Microsoft的实现使用结构体:
struct {
    union {
        void (*faddr)();     // 函数地址
        int delta;           // 虚函数表偏移
    };
    int index;               // -1表示非虚函数
    int vbase_offset;        // 虚基类偏移
    int vtbl_offset;         // 虚函数表偏移
};

4.5 内联函数(Inline Functions)

4.5.1 内联函数的处理
cpp 复制代码
// 内联函数定义
inline int min(int a, int b) {
    return a < b ? a : b;
}

// 使用
int result = min(x, y);

// 编译器展开后
int result = x < y ? x : y;
4.5.2 形式参数(Formal Arguments)
cpp 复制代码
inline int min(int a, int b) {
    return a < b ? a : b;
}

// 有副作用的参数
int result = min(foo(), bar());

// 不能简单展开为:
// int result = foo() < bar() ? foo() : bar();
// 因为foo()会被调用两次

// 需要引入临时变量:
int t1 = foo();
int t2 = bar();
int result = t1 < t2 ? t1 : t2;
4.5.3 局部变量(Local Variables)
cpp 复制代码
inline int complex_min(int a, int b) {
    int minval = a < b ? a : b;
    return minval;
}

// 使用在表达式中
int result = complex_min(x, y) + complex_min(p, q);

// 展开需要避免名称冲突:
int __min_lv_minval_2 = x < y ? x : y;
int __min_lv_minval_5 = p < q ? p : q;
int result = __min_lv_minval_2 + __min_lv_minval_5;

5. 构造、析构、拷贝语义学

5.1 无继承情况下的对象构造

5.1.1 抽象数据类型(Abstract Data Type)
cpp 复制代码
class Point {
    float x, y, z;
public:
    Point(float a = 0.0, float b = 0.0, float c = 0.0)
        : x(a), y(b), z(c) { }
};

// 构造函数被展开为:
inline void Point_constructor(Point *this, 
                            float a = 0.0, 
                            float b = 0.0, 
                            float c = 0.0) {
    this->x = a;
    this->y = b;
    this->z = c;
}

// 对象定义
Point p1;  // Point_constructor(&p1, 0.0, 0.0, 0.0);
Point p2(1.0, 2.0, 3.0);  // Point_constructor(&p2, 1.0, 2.0, 3.0);
5.1.2 为继承做准备
cpp 复制代码
class Point {
    float x, y, z;
public:
    Point(float a = 0.0, float b = 0.0, float c = 0.0)
        : x(a), y(b), z(c) { }
    
    virtual float magnitude() const {
        return sqrt(x*x + y*y + z*z);
    }
};

// 编译器扩充的构造函数:
inline void Point_constructor(Point *this, 
                            float a = 0.0, 
                            float b = 0.0, 
                            float c = 0.0) {
    // 1. 设置虚函数表指针
    this->__vptr = &Point_vtbl;
    
    // 2. 用户定义的代码
    this->x = a;
    this->y = b;
    this->z = c;
}

5.2 继承体系下的对象构造

5.2.1 虚拟继承(Virtual Inheritance)
cpp 复制代码
class Point2d {
protected:
    float x, y;
public:
    Point2d(float a = 0.0, float b = 0.0) : x(a), y(b) { }
    virtual void print() { }
};

class Vertex : virtual public Point2d {
protected:
    Vertex *next;
public:
    Vertex(float a = 0.0, float b = 0.0) 
        : Point2d(a, b), next(0) { }
};

class Point3d : virtual public Point2d {
protected:
    float z;
public:
    Point3d(float a = 0.0, float b = 0.0, float c = 0.0)
        : Point2d(a, b), z(c) { }
};

class Vertex3d : public Vertex, public Point3d {
protected:
    float mumble;
public:
    Vertex3d(float a = 0.0, float b = 0.0, float c = 0.0)
        : Point2d(a, b), Vertex(a, b), 
          Point3d(a, b, c), mumble(0) { }
};

构造函数的扩充:

cpp 复制代码
// Vertex3d构造函数的编译器扩充版本
void Vertex3d_constructor(Vertex3d *this, 
                         float a = 0.0, 
                         float b = 0.0, 
                         float c = 0.0) {
    // 1. 调用虚基类构造函数(只在最底层类调用)
    Point2d_constructor(this + __vbase_offset, a, b);
    
    // 2. 调用直接基类构造函数(跳过虚基类)
    Vertex_constructor_without_vbase(this, a, b);
    Point3d_constructor_without_vbase(this + sizeof(Vertex), a, b, c);
    
    // 3. 设置vptr
    this->__vptr_Vertex = &Vertex3d_Vertex_vtbl;
    this->__vptr_Point3d = &Vertex3d_Point3d_vtbl;
    
    // 4. 执行用户代码
    this->mumble = 0;
}
5.2.2 vptr初始化语义学(The Semantics of the vptr Initialization)

vptr的设置时机很关键:

cpp 复制代码
class Base {
public:
    Base() { 
        // vptr在这里指向Base::vtbl
        vfunc();  // 调用Base::vfunc,不是派生类的!
    }
    virtual void vfunc() { cout << "Base::vfunc" << endl; }
};

class Derived : public Base {
public:
    Derived() : Base() {
        // Base构造完成后,vptr被更新为Derived::vtbl
        vfunc();  // 调用Derived::vfunc
    }
    void vfunc() override { cout << "Derived::vfunc" << endl; }
};

// 构造Derived对象时的输出:
// Base::vfunc
// Derived::vfunc

构造函数中vptr的演化:

cpp 复制代码
// PVertex构造函数(假设)
PVertex::PVertex(float a, float b, float c)
    : Point3d(a, b, c), Vertex3d(a, b), Point(a) {
    
    // 在每个基类构造函数返回后,更新vptr
    // Point构造后:vptr = Point::vtbl
    // Point3d构造后:vptr = Point3d::vtbl  
    // Vertex3d构造后:vptr = Vertex3d::vtbl
    // 最后:vptr = PVertex::vtbl
}

5.3 对象的拷贝语义学(Object Copy Semantics)

5.3.1 拷贝赋值操作符(Copy Assignment Operator)
cpp 复制代码
class Point {
    float x, y, z;
public:
    Point& operator=(const Point& rhs) {
        if (this != &rhs) {  // 自我赋值检查
            x = rhs.x;
            y = rhs.y;
            z = rhs.z;
        }
        return *this;
    }
};

在继承体系中:

cpp 复制代码
class Point3d : public Point {
    float w;
public:
    Point3d& operator=(const Point3d& rhs) {
        if (this != &rhs) {
            Point::operator=(rhs);  // 调用基类赋值
            w = rhs.w;
        }
        return *this;
    }
};
5.3.2 虚拟基类的拷贝赋值

虚拟基类带来特殊问题:

cpp 复制代码
class Vertex3d : public Vertex, public Point3d {
public:
    Vertex3d& operator=(const Vertex3d& rhs) {
        if (this != &rhs) {
            // 只调用一次虚基类的赋值
            Point2d::operator=(rhs);
            
            // 调用直接基类(跳过虚基类)
            Vertex::operator_assign_without_vbase(rhs);
            Point3d::operator_assign_without_vbase(rhs);
            
            // 赋值自己的成员
            mumble = rhs.mumble;
        }
        return *this;
    }
};

5.4 对象的功能(Object Efficiency)

5.4.1 析构语义学(Semantics of Destruction)

析构函数的调用顺序与构造相反:

cpp 复制代码
class PVertex : public Vertex3d {
    float *ptr;
public:
    ~PVertex() {
        // 1. 用户定义的析构代码
        delete[] ptr;
        
        // 2. 编译器插入的代码(逆序):
        // - 析构成员对象(如果有)
        // - 调用直接基类析构函数
        // - 调用虚基类析构函数(只在最底层)
    }
};

// 编译器扩充:
void PVertex_destructor(PVertex *this) {
    // 1. 设置vptr(对于虚析构函数)
    this->__vptr = &PVertex::vtbl;
    
    // 2. 用户代码
    delete[] this->ptr;
    
    // 3. 调用成员析构(逆序)
    
    // 4. 调用基类析构
    Vertex3d_destructor_without_vbase(this);
    
    // 5. 调用虚基类析构(如果这是最底层类)
    if (this->__most_derived) {
        Point2d_destructor(this + __vbase_offset);
    }
}

5.5 全局对象(Global Objects)

5.5.1 静态初始化(Static Initialization)
cpp 复制代码
// 全局对象
Matrix identity;
int main() {
    // identity必须在main之前构造
    return 0;
}

// 编译器生成的代码
// 1. 静态初始化函数
void __sti__matrix_c_identity() {
    identity.Matrix::Matrix();
}

// 2. 静态析构函数
void __std__matrix_c_identity() {
    identity.Matrix::~Matrix();
}

// 3. 注册到启动/终止链表
__sti__matrix_c_identity();
atexit(__std__matrix_c_identity);
5.5.2 局部静态对象(Local Static Objects)
cpp 复制代码
const Matrix& identity() {
    static Matrix mat;
    return mat;
}

// 编译器转化:
const Matrix& identity() {
    static bool __initialized = false;
    static char __mat[sizeof(Matrix)];
    
    if (!__initialized) {
        __initialized = true;
        new (__mat) Matrix();  // placement new
        atexit(destructor_for_mat);
    }
    
    return *(Matrix*)__mat;
}

5.6 对象数组(Array of Objects)

5.6.1 数组的构造
cpp 复制代码
Point array[10];

// 编译器生成类似:
for (int i = 0; i < 10; ++i) {
    array[i].Point::Point();
}

// 带参数的构造
Point array2[3] = {
    Point(1, 2, 3),
    Point(4, 5, 6),
    Point(7, 8, 9)
};
5.6.2 new和delete数组
cpp 复制代码
Point *array = new Point[10];

// 实际的内存分配
// 1. 分配sizeof(Point) * 10 + sizeof(int)
// 2. 在开头存储元素个数10
// 3. 返回偏移后的指针

delete[] array;

// delete[]的实现:
// 1. 获取元素个数(从array-sizeof(int)读取)
// 2. 逆序调用析构函数
// 3. 释放内存(包括计数器)

6. 执行期语义学(Runtime Semantics)

6.1 对象的构造和析构(Object Construction and Destruction)

6.1.1 全局对象的静态初始化
cpp 复制代码
// file1.cpp
FileTable table;

// file2.cpp
Account global_account("John", 1000);

// 问题:初始化顺序是未定义的!
// table和global_account谁先初始化?

解决方案:

cpp 复制代码
// 使用Schwarz计数器(Reference Counting)
class initializer {
    static int count;
public:
    initializer() {
        if (count++ == 0) {
            // 执行初始化
        }
    }
    ~initializer() {
        if (--count == 0) {
            // 执行清理
        }
    }
};

// 在每个需要的文件中
static initializer __init;
6.1.2 局部静态对象(Local Static Objects)
cpp 复制代码
Shape& make_shape(int choice) {
    switch(choice) {
    case 1:
        static Circle c;
        return c;
    case 2:
        static Rectangle r;
        return r;
    default:
        static Triangle t;
        return t;
    }
}

// 编译器必须保证:
// 1. 每个静态对象只初始化一次
// 2. 在函数返回前注册析构函数
// 3. 线程安全(C++11)

6.2 new和delete运算符(Operators new and delete)

6.2.1 new运算符的实现
cpp 复制代码
// new表达式
Point3d *p = new Point3d(1.0, 2.0, 3.0);

// 分解为两步:
// 1. 分配内存
Point3d *p = (Point3d*) operator new(sizeof(Point3d));

// 2. 构造对象
try {
    p->Point3d::Point3d(1.0, 2.0, 3.0);
} catch(...) {
    // 构造失败,释放内存
    operator delete(p);
    throw;
}
6.2.2 数组的new
cpp 复制代码
int *pi = new int[10];
// 简单分配,不需要构造

Point3d *parr = new Point3d[10];
// 转化为:
// 1. 计算需要的内存
size_t size = sizeof(Point3d) * 10 + sizeof(int);

// 2. 分配内存并存储计数
char *mem = (char*) operator new(size);
*(int*)mem = 10;  // 存储元素个数
Point3d *parr = (Point3d*)(mem + sizeof(int));

// 3. 构造每个元素
for (int i = 0; i < 10; ++i) {
    new (&parr[i]) Point3d();  // placement new
}
6.2.3 placement new
cpp 复制代码
// 在已分配的内存上构造对象
char buffer[sizeof(Point3d)];
Point3d *p = new (buffer) Point3d(1.0, 2.0, 3.0);

// placement new的声明
void* operator new(size_t, void *p) { return p; }

// 使用场景:内存池
class MemoryPool {
    char pool[1000 * sizeof(Point3d)];
    int next_free = 0;
    
public:
    Point3d* allocate() {
        void *mem = &pool[next_free * sizeof(Point3d)];
        next_free++;
        return new (mem) Point3d();
    }
};

6.3 临时对象(Temporary Objects)

6.3.1 临时对象的生命周期
cpp 复制代码
// 情况1:绑定到const引用
const String& s = String("temporary");
// 临时对象的生命周期延长到引用的生命周期

// 情况2:完整表达式结束
String s1 = "hello";
String s2 = "world";  
String s3 = s1 + " " + s2;
// s1 + " "产生的临时对象在整个表达式结束后销毁

// 情况3:函数参数
void print(String s);
print(String("temp"));  // 临时对象在函数调用后销毁
6.3.2 临时对象的优化

编译器可能的优化:

cpp 复制代码
// 返回值优化(RVO)
Matrix operator+(const Matrix& a, const Matrix& b) {
    Matrix result;
    // ... 计算
    return result;  // 可能直接在返回位置构造
}

// 使用时
Matrix m3 = m1 + m2;  // 不产生临时对象

// 编译器转化为:
Matrix m3;  // 不初始化
operator+(&m3, m1, m2);  // 直接在m3位置构造结果

6.4 对象的生命期(Object Lifetime)

6.4.1 对象生命期的概念

对象的生命期:

  • 开始:构造函数成功完成
  • 结束:析构函数开始执行
cpp 复制代码
class FileHandler {
    FILE* file;
public:
    FileHandler(const char* name) {
        file = fopen(name, "r");
        if (!file) throw runtime_error("Cannot open file");
        // 生命期从这里开始
    }
    
    ~FileHandler() {
        // 生命期在这里结束
        if (file) fclose(file);
    }
};
6.4.2 条件性构造
cpp 复制代码
void conditional_construction(bool flag) {
    Point p1;  // 总是构造
    
    if (flag) {
        Point p2;  // 条件性构造
    }  // p2在这里析构(如果被构造)
    
    // p1在这里析构
}

// 使用goto的复杂情况
void complex_flow() {
    goto label;
    
    Point p;  // 错误!跳过了构造
    
label:
    // ...
}

7. 站在对象模型的尖端

7.1 Template(模板)

7.1.1 Template的实例化(Template Instantiation)
cpp 复制代码
template <class T>
class Point {
    T x, y, z;
public:
    Point(T a = T(), T b = T(), T c = T()) 
        : x(a), y(b), z(c) { }
    
    T magnitude() const {
        return sqrt(x*x + y*y + z*z);
    }
};

// 使用时产生实例化
Point<float> pf;     // 实例化Point<float>
Point<double> pd;    // 实例化Point<double>

// 每个实例化产生独立的类型
// sizeof(Point<float>) != sizeof(Point<double>)
7.1.2 Template的错误报告
cpp 复制代码
template <class T>
class Array {
    T* data;
    int size;
public:
    T& operator[](int index) {
        return data[index];  // 没有边界检查
    }
};

// 模板定义时不报错
// 实例化时才可能报错
Array<int> ai;      // OK
Array<void> av;     // 错误:void[]是非法的
7.1.3 Template的实例化策略

1. 包含模型(Inclusion Model)

cpp 复制代码
// point.h
template <class T>
class Point {
    // ... 完整定义
};

// 每个使用Point的源文件都包含完整定义
// 可能导致代码膨胀

2. 分离模型(Separation Model)

cpp 复制代码
// point.h
export template <class T>
class Point {
    T magnitude() const;
};

// point.cpp
template <class T>
T Point<T>::magnitude() const {
    // 实现
}

3. 显式实例化(Explicit Instantiation)

cpp 复制代码
// point.cpp
template class Point<float>;   // 显式实例化
template class Point<double>;

7.2 异常处理(Exception Handling)

7.2.1 异常处理的对象模型
cpp 复制代码
void foo() {
    Bar b;
    try {
        Baz bz;
        // ... 可能抛出异常的代码
    }
    catch (Exception& e) {
        // 处理异常
    }
    // b和bz(如果构造了)必须被正确析构
}

// 编译器生成的伪代码
void foo() {
    // 异常处理表
    exception_table_entry table[] = {
        { try_start, try_end, catch_handler, &Exception::typeinfo }
    };
    
    Bar b;
    
try_start:
    Baz bz;
    // ... 代码
    goto try_end;
    
catch_handler:
    // 栈展开已经析构了bz
    Exception& e = get_exception_object();
    // 处理异常
    
try_end:
    // 正常路径
}
7.2.2 异常处理的成本
cpp 复制代码
// 零成本模型(Zero-Cost Model)
// 正常执行路径没有额外开销
// 只在抛出异常时才有成本

// 表格驱动方法
struct exception_table_entry {
    void* start_pc;    // try块开始
    void* end_pc;      // try块结束
    void* handler_pc;  // catch处理器
    const type_info* catch_type;  // 捕获的类型
};

// 当异常发生时:
// 1. 查找当前PC对应的异常表项
// 2. 进行类型匹配
// 3. 栈展开
// 4. 跳转到处理器
7.2.3 支持异常处理的对象构造
cpp 复制代码
class X {
    A a;
    B b;
    C c;
public:
    X() : a(), b(), c() { }
    // 如果b的构造函数抛出异常
    // a必须被析构,但c不会被构造
};

// 编译器生成的代码
X::X() {
    // 构造a
    try {
        a.A::A();
    } catch(...) {
        // 没有东西需要清理
        throw;
    }
    
    // 构造b
    try {
        b.B::B();
    } catch(...) {
        a.A::~A();  // 清理a
        throw;
    }
    
    // 构造c
    try {
        c.C::C();
    } catch(...) {
        b.B::~B();  // 清理b
        a.A::~A();  // 清理a
        throw;
    }
}

7.3 执行期类型识别(Runtime Type Identification, RTTI)

7.3.1 RTTI的实现
cpp 复制代码
class type_info {
    const char* name;
    // 其他实现细节
public:
    const char* name() const { return name; }
    bool operator==(const type_info& rhs) const;
    bool before(const type_info& rhs) const;
};

// 每个多态类的vtbl扩展
struct vtbl_prefix {
    const type_info* type;  // 指向类型信息
    // ... 虚函数指针数组
};

class Base {
    virtual ~Base() { }
};

class Derived : public Base { };

// 使用RTTI
Base* pb = new Derived;
const type_info& ti = typeid(*pb);
cout << ti.name() << endl;  // "Derived"
7.3.2 dynamic_cast的实现
cpp 复制代码
// dynamic_cast的几种情况

// 1. 向下转型(downcast)
Base* pb = new Derived;
Derived* pd = dynamic_cast<Derived*>(pb);

// 实现伪代码
Derived* dynamic_cast_impl(Base* pb) {
    if (pb == nullptr) return nullptr;
    
    const type_info& obj_type = typeid(*pb);
    if (obj_type == typeid(Derived)) {
        return static_cast<Derived*>(pb);
    }
    
    // 检查是否是Derived的派生类
    if (is_derived_from(obj_type, typeid(Derived))) {
        return static_cast<Derived*>(pb);
    }
    
    return nullptr;
}

// 2. 交叉转型(crosscast)
class A { virtual ~A() { } };
class B { virtual ~B() { } };
class C : public A, public B { };

A* pa = new C;
B* pb = dynamic_cast<B*>(pa);  // 需要调整指针

7.4 效率有了,弹性呢?(Efficiency and Flexibility)

7.4.1 动态共享库(Dynamic Shared Libraries)
cpp 复制代码
// 跨动态库边界的对象模型问题

// library.h - 版本1
class Widget {
    int x, y;
public:
    virtual void draw();
};

// 用户代码编译时链接版本1

// library.h - 版本2(添加了成员)
class Widget {
    int x, y, z;  // 新增成员
public:
    virtual void draw();
    virtual void resize();  // 新增虚函数
};

// 问题:对象大小改变,vtbl布局改变
// 旧代码无法正确使用新库

解决方案:

cpp 复制代码
// 使用接口类
class IWidget {
public:
    virtual ~IWidget() { }
    virtual void draw() = 0;
    
    // 工厂函数
    static IWidget* create();
};

// 实现细节隐藏在库内部
class WidgetImpl : public IWidget {
    // 可以自由修改
};
7.4.2 共享内存(Shared Memory)
cpp 复制代码
// 在共享内存中放置C++对象的挑战

// 1. 虚函数表指针问题
class Shared {
    virtual void foo();
};
// vptr在不同进程中可能不同

// 2. 指针成员问题  
class Node {
    Node* next;  // 在不同进程中地址不同
};

// 解决方案:使用偏移量而非指针
template <class T>
class shared_ptr {
    ptrdiff_t offset;  // 相对于某个基地址的偏移
public:
    T* get() const {
        return offset ? (T*)((char*)base_addr + offset) : nullptr;
    }
};
相关推荐
love530love7 分钟前
【笔记】PyCharm 使用问题反馈与官方进展速览
ide·人工智能·笔记·python·pycharm
xtmatao39 分钟前
WIN11+VSCODE搭建的c/c++环境调试报错解决
c语言·c++·vscode
cpp加油站1 小时前
(保姆级教程)Trae中使用clangd插件实现c++代码函数列表、变量补全、代码跳转等功能
c++·ai编程·trae
鑫鑫向栄1 小时前
[蓝桥杯]填字母游戏
数据结构·c++·算法·蓝桥杯·深度优先
可信计算1 小时前
[AI Claude] 软件测试1
笔记
筏.k1 小时前
C++ 新特性详解:Lambda 表达式全解析(含实战案例)
c语言·开发语言·c++
William Dawson2 小时前
【从前端到后端导入excel文件实现批量导入-笔记模仿芋道源码的《系统管理-用户管理-导入-批量导入》】
java·前端·笔记·elementui·typescript·excel
将编程培养成爱好3 小时前
《复制粘贴的奇迹:小明的原型工厂》
c++·设计模式·原型模式
曾几何时`3 小时前
C++——智能指针 weak_ptr
开发语言·c++
<但凡.3 小时前
C++修炼:C++11(一)
开发语言·c++