《深度探索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 |
// +---------+
//
// 静态数据成员和所有函数都在对象之外
核心特征:
-
非静态数据成员被配置在每一个类对象内
-
静态数据成员被存放在所有类对象之外
-
静态和非静态函数成员被放在所有类对象之外
-
虚函数
通过两个步骤支持:
- 每个类产生一个虚函数表(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:
- 当你需要与C兼容的数据布局时
- 当所有数据都是public且没有函数时(POD - Plain Old Data)
- 当你需要明确的内存布局控制时
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++支持三种程序设计范型:
- 程序型(Procedural):C风格
- 抽象数据类型(ADT):封装
- 面向对象(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 对象的内存需求
对象的内存大小包括:
- 非静态数据成员的总和
- 由于对齐(alignment)而填补的空间
- 为了支持虚函数而产生的额外负担(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 |
// +-------------+
多态的主要成本:
- 每个对象增加一个vptr(通常4或8字节)
- 每个类增加一个vtbl
- 虚函数调用的间接性(通过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 指针类型的意义
指针的类型决定了:
- 编译时决议:指针可以访问的接口
- 运行时决议:虚函数的实际调用
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++标准保证:
- 同一访问级别中,成员的排列顺序与声明顺序一致
- 后声明的成员在对象中有较高的地址
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 |
// +-------------+
多态引入的额外成本:
- 每个对象增加一个vptr
- 每个类需要一个vtbl
- 构造函数需要设置vptr
- 析构函数需要通过虚函数机制调用
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; // 需要间接存取
}
效率排序(从高到低):
- 独立类 ≈ 单一继承(无虚函数)
- 单一继承(有虚函数)
- 多重继承
- 虚拟继承
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)
静态成员函数的特点:
- 没有this指针
- 不能直接存取非静态成员
- 不能声明为const、volatile或virtual
- 不需要通过对象调用
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(); // 普通函数调用
}
性能排序(从快到慢):
- 内联函数
- 静态成员函数 ≈ 非成员函数
- 非虚成员函数
- 单一继承的虚函数
- 多重继承的虚函数(需要调整this)
- 虚拟继承的虚函数
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;
}
};