《深度探索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;}
};