《深度探索C++对象模型》阅读笔记(完整版)
《深度探索C++对象模型》阅读笔记(完整版)
文章目录
- 《深度探索C++对象模型》阅读笔记(完整版)
- 1. 关于对象(Object Lessons)
- 1.1 C++对象模型(The C++ Object Model)
- 1.1.1 语言中的对象模型
- 1.1.2 简单对象模型(A Simple Object Model)
- 1.1.3 表格驱动对象模型(A Table-driven Object Model)
- 1.1.4 C++对象模型(The C++ Object Model)
- 1.2 关键词的差异(A Keyword Distinction)
- 1.2.1 struct vs class
- 1.2.2 策略性正确的struct
- 1.3 对象的差异(An Object Distinction)
- 1.3.1 程序设计范型(Programming Paradigms)
- 1.3.2 对象的内存需求
- 1.3.3 多态的成本
- 1.4 指针的类型(The Type of a Pointer)
- 1.4.1 指针类型的意义
- 1.4.2 转型(Cast)操作
- 2. 构造函数语义学(The Semantics of Constructors)
- 2.1 默认构造函数的构造操作(Default Constructor Construction)
- 2.1.1 编译器何时生成默认构造函数
- 2.1.2 被合成的默认构造函数的行为
- 2.2 拷贝构造函数的构造操作(Copy Constructor Construction)
- 2.2.1 默认成员初始化(Default Memberwise Initialization)
- 2.2.2 位逐次拷贝(Bitwise Copy Semantics)
- 2.2.3 不能使用位逐次拷贝的情况
- 2.3 程序转化语义(Program Transformation Semantics)
- 2.3.1 显式的初始化操作(Explicit Initialization)
- 2.3.2 参数的初始化(Argument Initialization)
- 2.3.3 返回值的初始化(Return Value Initialization)
- 2.3.4 Named Return Value (NRV) 优化
- 2.4 成员初始化列表(Member Initialization List)
- 2.4.1 必须使用初始化列表的情况
- 2.4.2 初始化顺序
- 2.4.3 初始化列表的效率
- 3. Data语义学(The Semantics of Data)
- 3.1 Data Member的布局(The Binding of a Data Member)
- 3.1.1 数据成员的布局规则
- 3.1.2 访问级别对布局的影响
- 3.1.3 边界对齐(Alignment)
- 3.2 Data Member的存取(Data Member Access)
- 3.2.1 静态数据成员(Static Data Members)
- 3.2.2 非静态数据成员(Nonstatic Data Members)
- 3.2.3 通过指针访问
- 3.3 继承与Data Member(Inheritance and the Data Member)
- 3.3.1 只有继承没有多态
- 3.3.2 加上多态
- 3.3.3 多重继承(Multiple Inheritance)
- 3.3.4 虚拟继承(Virtual Inheritance)
- 3.4 对象成员的效率(Object Member Efficiency)
- 3.4.1 聚合(Aggregation)vs 继承
- 3.4.2 不同继承模型的效率比较
- 3.5 指向成员的指针(Pointer to Data Members)
- 4. Function语义学(The Semantics of Function)
- 4.1 Member的各种调用方式(Varieties of Member Invocation)
- 4.1.1 非静态成员函数(Nonstatic Member Functions)
- 4.1.2 虚函数(Virtual Member Functions)
- 4.1.3 静态成员函数(Static Member Functions)
- 4.2 虚函数机制(Virtual Member Functions)
- 4.2.1 单一继承下的虚函数
- 4.2.2 多重继承下的虚函数
- 4.2.3 虚拟继承下的虚函数
- 4.3 函数的效能(Function Efficiency)
- 4.3.1 各种函数调用的比较
- 4.4 指向成员函数的指针(Pointer-to-Member Functions)
- 4.4.1 指向非虚成员函数的指针
- 4.4.2 指向虚函数的指针
- 4.4.3 多重继承下的成员函数指针
- 4.5 内联函数(Inline Functions)
- 4.5.1 内联函数的处理
- 4.5.2 形式参数(Formal Arguments)
- 4.5.3 局部变量(Local Variables)
- 5. 构造、析构、拷贝语义学
- 5.1 无继承情况下的对象构造
- 5.1.1 抽象数据类型(Abstract Data Type)
- 5.1.2 为继承做准备
- 5.2 继承体系下的对象构造
- 5.2.1 虚拟继承(Virtual Inheritance)
- 5.2.2 vptr初始化语义学(The Semantics of the vptr Initialization)
- 5.3 对象的拷贝语义学(Object Copy Semantics)
- 5.3.1 拷贝赋值操作符(Copy Assignment Operator)
- 5.3.2 虚拟基类的拷贝赋值
- 5.4 对象的功能(Object Efficiency)
- 5.4.1 析构语义学(Semantics of Destruction)
- 5.5 全局对象(Global Objects)
- 5.5.1 静态初始化(Static Initialization)
- 5.5.2 局部静态对象(Local Static Objects)
- 5.6 对象数组(Array of Objects)
- 5.6.1 数组的构造
- 5.6.2 new和delete数组
- 6. 执行期语义学(Runtime Semantics)
- 6.1 对象的构造和析构(Object Construction and Destruction)
- 6.1.1 全局对象的静态初始化
- 6.1.2 局部静态对象(Local Static Objects)
- 6.2 new和delete运算符(Operators new and delete)
- 6.2.1 new运算符的实现
- 6.2.2 数组的new
- 6.2.3 placement new
- 6.3 临时对象(Temporary Objects)
- 6.3.1 临时对象的生命周期
- 6.3.2 临时对象的优化
- 6.4 对象的生命期(Object Lifetime)
- 6.4.1 对象生命期的概念
- 6.4.2 条件性构造
- 7. 站在对象模型的尖端
- 7.1 Template(模板)
- 7.1.1 Template的实例化(Template Instantiation)
- 7.1.2 Template的错误报告
- 7.1.3 Template的实例化策略
- 7.2 异常处理(Exception Handling)
- 7.2.1 异常处理的对象模型
- 7.2.2 异常处理的成本
- 7.2.3 支持异常处理的对象构造
- 7.3 执行期类型识别(Runtime Type Identification, RTTI)
- 7.3.1 RTTI的实现
- 7.3.2 dynamic_cast的实现
- 7.4 效率有了,弹性呢?(Efficiency and Flexibility)
- 7.4.1 动态共享库(Dynamic Shared Libraries)
- 7.4.2 共享内存(Shared Memory)
1. 关于对象(Object Lessons)
1.1 C++对象模型(The C++ Object Model)
1.1.1 语言中的对象模型
在C语言中,"数据"和"处理数据的操作"是分开声明的,语言本身并没有支持"数据和函数"之间的关联性。而C++通过抽象数据类型(ADT)将数据和操作封装在一起。
// 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),每个槽指向一个成员。成员按声明顺序排列,包括数据成员和函数成员。
// 概念模型
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)
这个模型把所有与成员相关的信息抽出来,放在一个数据成员表和一个函数成员表中,对象本身只含有指向这两个表的指针。
// 概念示意
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++对象模型从简单对象模型派生而来,对内存和存取时间做了优化:
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 |
// +---------+
//
// 静态数据成员和所有函数都在对象之外
核心特征:
-
非静态数据成员被配置在每一个类对象内
-
静态数据成员被存放在所有类对象之外
-
静态和非静态函数成员被放在所有类对象之外
-
虚函数
通过两个步骤支持:
- 每个类产生一个虚函数表(vtbl)
- 每个类对象添加一个指针(vptr),指向相关的虚函数表
1.2 关键词的差异(A Keyword Distinction)
1.2.1 struct vs class
在C++中,struct和class的唯一差别是默认访问级别:
- struct默认是public
- class默认是private
struct S {int x; // 默认publicvoid f(); // 默认public
};class C {int x; // 默认private
public:void f(); // 明确声明public
};
1.2.2 策略性正确的struct
什么时候应该使用struct:
- 当你需要与C兼容的数据布局时
- 当所有数据都是public且没有函数时(POD - Plain Old Data)
- 当你需要明确的内存布局控制时
// 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++支持三种程序设计范型:
- 程序型(Procedural):C风格
- 抽象数据类型(ADT):封装
- 面向对象(Object-Oriented):继承和多态
// 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 对象的内存需求
对象的内存大小包括:
- 非静态数据成员的总和
- 由于对齐(alignment)而填补的空间
- 为了支持虚函数而产生的额外负担(vptr)
class A {char c; // 1 byteint 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) = 8class C : public A {char c2; // 1 byte// 3 bytes padding
}; // sizeof(C) = 12
1.3.3 多态的成本
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 |
// +-------------+
多态的主要成本:
- 每个对象增加一个vptr(通常4或8字节)
- 每个类增加一个vtbl
- 虚函数调用的间接性(通过vtbl)
1.4 指针的类型(The Type of a Pointer)
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 指针类型的意义
指针的类型决定了:
- 编译时决议:指针可以访问的接口
- 运行时决议:虚函数的实际调用
// 指针的内存布局理解
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)操作
// 向上转型(安全)
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:成员对象带有默认构造函数
class Foo {
public:Foo() { cout << "Foo::Foo()" << endl; }
};class Bar {Foo foo; // Foo有默认构造函数char *str; // 内置类型,不会被初始化// 编译器生成的默认构造函数类似于:// Bar() { // foo.Foo::Foo(); // 调用Foo的默认构造函数// // str不会被初始化!// }
};
情况2:基类带有默认构造函数
class Base {
public:Base() { x = 0; }
private:int x;
};class Derived : public Base {int y; // 不会被初始化// 编译器生成的默认构造函数:// Derived() {// Base::Base(); // 调用基类构造函数// // y不会被初始化// }
};
情况3:带有虚函数的类
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:带有虚基类的类
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 被合成的默认构造函数的行为
重要概念:编译器合成的默认构造函数只满足编译器的需要,而不是程序的需要。
class Point {float x, y, z;
};// Point没有默认构造函数!
// 因为编译器不需要为Point做任何事情
// x, y, z不会被初始化为0void 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)
当没有提供显式的拷贝构造函数时,编译器会进行默认成员初始化:
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)
如果一个类没有定义拷贝构造函数,编译器会分析是否可以使用位逐次拷贝:
// 可以使用位逐次拷贝的情况
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:类包含有拷贝构造函数的成员对象
class Word {String str; // String有拷贝构造函数int occurs;// 编译器生成:// Word(const Word& w) // : str(w.str), // 调用String拷贝构造// occurs(w.occurs) // 位拷贝// { }
};
情况2:类继承自有拷贝构造函数的基类
class TextWord : public Word {Text *text;// 编译器生成:// TextWord(const TextWord& tw)// : Word(tw), // 调用基类拷贝构造// text(tw.text) // 位拷贝指针// { }
};
情况3:类声明了虚函数
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:类派生自继承链中有虚基类
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)
编译器如何转化初始化操作:
// 原始代码
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)
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)
X bar() {X xx;// 处理xxreturn 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) 优化
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 必须使用初始化列表的情况
以下四种情况必须使用成员初始化列表:
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 初始化顺序
成员初始化的顺序是由声明顺序决定的,而不是初始化列表的顺序:
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 初始化列表的效率
class Word {String name;int count;public:// 低效版本Word() {name = "default"; // 先默认构造,再赋值count = 0;}// 高效版本 Word() : name("default"), count(0) {// 直接构造,避免额外的默认构造}
};
编译器对初始化列表的扩展:
// 原始代码
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++标准保证:
- 同一访问级别中,成员的排列顺序与声明顺序一致
- 后声明的成员在对象中有较高的地址
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)之间的相对顺序是未定义的:
class Complex {
public:double real; // 第一个访问块private:double imag; // 第二个访问块public:int id; // 第三个访问块
};// 可能的布局1:按声明顺序
// real -> imag -> id// 可能的布局2:合并public块
// real -> id -> imag
3.1.3 边界对齐(Alignment)
class Aligned {char c1; // 1 byte// 3 bytes padding (假设int需要4字节对齐)int i; // 4 byteschar c2; // 1 byte // 3 bytes padding
}; // sizeof = 12// 优化布局
class Optimized {int i; // 4 byteschar c1; // 1 bytechar c2; // 1 byte// 2 bytes padding
}; // sizeof = 8
3.2 Data Member的存取(Data Member Access)
3.2.1 静态数据成员(Static Data Members)
静态数据成员存储在程序的数据段,而不是类对象中:
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)
非静态数据成员的存取需要通过对象的起始地址加上偏移量:
Point3d origin;
origin.x = 0.0;// 编译器转化为:
&origin + (&Point3d::x - 1); // -1是为了区分空指针// 成员指针的表示
float Point3d::*pm = &Point3d::x;
// pm实际存储的是x在Point3d中的偏移量+1
3.2.3 通过指针访问
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 只有继承没有多态
// 没有虚函数的继承
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 加上多态
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 |
// +-------------+
多态引入的额外成本:
- 每个对象增加一个vptr
- 每个类需要一个vtbl
- 构造函数需要设置vptr
- 析构函数需要通过虚函数机制调用
3.3.3 多重继承(Multiple Inheritance)
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 |
// +-------------+
多重继承的复杂性:
Point3d p3d;
Vertex *pv = &p3d; // 需要调整指针!// pv指向Vertex子对象的起始位置
// 不是Point3d对象的起始位置
// pv = (Vertex*)((char*)&p3d + delta)
// 其中delta = offsetof(Point3d, Vertex)
3.3.4 虚拟继承(Virtual Inheritance)
虚拟继承解决菱形继承问题:
class ios { };
class istream : virtual public ios { };
class ostream : virtual public ios { };
class iostream : public istream, public ostream { };// 没有虚拟继承,iostream会有两份ios
// 使用虚拟继承,只有一份ios
虚拟继承的实现模型:
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 继承
// 聚合方式
class Point2d {float x, y;
};class Point3d {Point2d point2d; // 包含float z;
};// 继承方式
class Point3d : public Point2d {float z;
};// 两种方式的存取效率相同
// 但继承支持多态,聚合不支持
3.4.2 不同继承模型的效率比较
// 测试代码
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; // 需要间接存取
}
效率排序(从高到低):
- 独立类 ≈ 单一继承(无虚函数)
- 单一继承(有虚函数)
- 多重继承
- 虚拟继承
3.5 指向成员的指针(Pointer to Data Members)
class Point3d {
public:float x, y, z;
};// 定义指向成员的指针
float Point3d::*p1 = &Point3d::x;
float Point3d::*p2 = &Point3d::y;// 使用
Point3d obj;
obj.*p1 = 1.0; // 设置xPoint3d *ptr = &obj;
ptr->*p2 = 2.0; // 设置y// 实现细节
// &Point3d::x 返回x的偏移量+1
// 加1是为了区分空指针
在继承体系中的复杂性:
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++的设计准则之一:非静态成员函数至少必须和非成员函数有相同的效率。这是通过将成员函数转化为非成员函数实现的:
// 原始的成员函数
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):
class Point {
public:void x(float newX);float x();
};// 可能的名称修饰:
// x__5PointFf (void x(float))
// x__5PointFv (float x())
4.1.2 虚函数(Virtual Member Functions)
虚函数的调用通过虚函数表进行:
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的实现
虚函数调用的转化:
Point *ptr = new Point2d;
ptr->mult(2.0);// 编译器转化为:
(*ptr->vptr[1])(ptr, 2.0);
// vptr[1]指向Point2d::mult
4.1.3 静态成员函数(Static Member Functions)
静态成员函数的特点:
- 没有this指针
- 不能直接存取非静态成员
- 不能声明为const、volatile或virtual
- 不需要通过对象调用
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 单一继承下的虚函数
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 (新增)
虚函数调用的成本分析:
// 通过对象调用(编译时可确定)
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 多重继承下的虚函数
多重继承需要多个虚函数表:
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):
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 虚拟继承下的虚函数
虚拟继承使虚函数机制更加复杂:
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 各种函数调用的比较
// 测试类
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(); // 普通函数调用
}
性能排序(从快到慢):
- 内联函数
- 静态成员函数 ≈ 非成员函数
- 非虚成员函数
- 单一继承的虚函数
- 多重继承的虚函数(需要调整this)
- 虚拟继承的虚函数
4.4 指向成员函数的指针(Pointer-to-Member Functions)
4.4.1 指向非虚成员函数的指针
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 指向虚函数的指针
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 多重继承下的成员函数指针
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 内联函数的处理
// 内联函数定义
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)
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)
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)
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 为继承做准备
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)
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) { }
};
构造函数的扩充:
// 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. 设置vptrthis->__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的设置时机很关键:
class Base {
public:Base() { // vptr在这里指向Base::vtblvfunc(); // 调用Base::vfunc,不是派生类的!}virtual void vfunc() { cout << "Base::vfunc" << endl; }
};class Derived : public Base {
public:Derived() : Base() {// Base构造完成后,vptr被更新为Derived::vtblvfunc(); // 调用Derived::vfunc}void vfunc() override { cout << "Derived::vfunc" << endl; }
};// 构造Derived对象时的输出:
// Base::vfunc
// Derived::vfunc
构造函数中vptr的演化:
// 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)
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;}
};
在继承体系中:
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 虚拟基类的拷贝赋值
虚拟基类带来特殊问题:
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)
析构函数的调用顺序与构造相反:
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)
// 全局对象
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)
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 newatexit(destructor_for_mat);}return *(Matrix*)__mat;
}
5.6 对象数组(Array of Objects)
5.6.1 数组的构造
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数组
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 全局对象的静态初始化
// file1.cpp
FileTable table;// file2.cpp
Account global_account("John", 1000);// 问题:初始化顺序是未定义的!
// table和global_account谁先初始化?
解决方案:
// 使用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)
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运算符的实现
// 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
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
// 在已分配的内存上构造对象
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 临时对象的生命周期
// 情况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 临时对象的优化
编译器可能的优化:
// 返回值优化(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 对象生命期的概念
对象的生命期:
- 开始:构造函数成功完成
- 结束:析构函数开始执行
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 条件性构造
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)
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的错误报告
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)
// point.h
template <class T>
class Point {// ... 完整定义
};// 每个使用Point的源文件都包含完整定义
// 可能导致代码膨胀
2. 分离模型(Separation Model)
// point.h
export template <class T>
class Point {T magnitude() const;
};// point.cpp
template <class T>
T Point<T>::magnitude() const {// 实现
}
3. 显式实例化(Explicit Instantiation)
// point.cpp
template class Point<float>; // 显式实例化
template class Point<double>;
7.2 异常处理(Exception Handling)
7.2.1 异常处理的对象模型
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:// 栈展开已经析构了bzException& e = get_exception_object();// 处理异常try_end:// 正常路径
}
7.2.2 异常处理的成本
// 零成本模型(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 支持异常处理的对象构造
class X {A a;B b;C c;
public:X() : a(), b(), c() { }// 如果b的构造函数抛出异常// a必须被析构,但c不会被构造
};// 编译器生成的代码
X::X() {// 构造atry {a.A::A();} catch(...) {// 没有东西需要清理throw;}// 构造btry {b.B::B();} catch(...) {a.A::~A(); // 清理athrow;}// 构造ctry {c.C::C();} catch(...) {b.B::~B(); // 清理ba.A::~A(); // 清理athrow;}
}
7.3 执行期类型识别(Runtime Type Identification, RTTI)
7.3.1 RTTI的实现
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的实现
// 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)
// 跨动态库边界的对象模型问题// 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布局改变
// 旧代码无法正确使用新库
解决方案:
// 使用接口类
class IWidget {
public:virtual ~IWidget() { }virtual void draw() = 0;// 工厂函数static IWidget* create();
};// 实现细节隐藏在库内部
class WidgetImpl : public IWidget {// 可以自由修改
};
7.4.2 共享内存(Shared Memory)
// 在共享内存中放置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;}
};