《深度探索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       |
// +---------+
//
// 靜態數據成員和所有函數都在對象之外

核心特征:

  1. 非靜態數據成員被配置在每一個類對象內

  2. 靜態數據成員被存放在所有類對象之外

  3. 靜態和非靜態函數成員被放在所有類對象之外

  4. 虛函數

    通過兩個步驟支持:

    • 每個類產生一個虛函數表(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:

  1. 當你需要與C兼容的數據布局時
  2. 當所有數據都是public且沒有函數時(POD - Plain Old Data)
  3. 當你需要明確的內存布局控制時
// C兼容的struct
struct CCompatible {int id;char name[50];float value;
};// 可以安全地在C和C++之間傳遞
extern "C" void process_data(CCompatible* data);

1.3 對象的差異(An Object Distinction)

1.3.1 程序設計范型(Programming Paradigms)

C++支持三種程序設計范型:

  1. 程序型(Procedural):C風格
  2. 抽象數據類型(ADT):封裝
  3. 面向對象(Object-Oriented):繼承和多態
// 1. 程序型模型
char* strcpy(char* dest, const char* src);// 2. ADT模型
class String {char* data;
public:String(const char* str);String& operator=(const String& rhs);
};// 3. 面向對象模型
class Shape {
public:virtual void draw() = 0;virtual ~Shape() {}
};class Circle : public Shape {
public:void draw() override;
};
1.3.2 對象的內存需求

對象的內存大小包括:

  1. 非靜態數據成員的總和
  2. 由于對齊(alignment)而填補的空間
  3. 為了支持虛函數而產生的額外負擔(vptr)
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  |
// +-------------+

多態的主要成本:

  1. 每個對象增加一個vptr(通常4或8字節)
  2. 每個類增加一個vtbl
  3. 虛函數調用的間接性(通過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 指針類型的意義

指針的類型決定了:

  1. 編譯時決議:指針可以訪問的接口
  2. 運行時決議:虛函數的實際調用
// 指針的內存布局理解
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++標準保證:

  1. 同一訪問級別中,成員的排列順序與聲明順序一致
  2. 后聲明的成員在對象中有較高的地址
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           |
// +-------------+

多態引入的額外成本:

  1. 每個對象增加一個vptr
  2. 每個類需要一個vtbl
  3. 構造函數需要設置vptr
  4. 析構函數需要通過虛函數機制調用
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;  // 需要間接存取
}

效率排序(從高到低):

  1. 獨立類 ≈ 單一繼承(無虛函數)
  2. 單一繼承(有虛函數)
  3. 多重繼承
  4. 虛擬繼承

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)

靜態成員函數的特點:

  1. 沒有this指針
  2. 不能直接存取非靜態成員
  3. 不能聲明為const、volatile或virtual
  4. 不需要通過對象調用
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();  // 普通函數調用
}

性能排序(從快到慢):

  1. 內聯函數
  2. 靜態成員函數 ≈ 非成員函數
  3. 非虛成員函數
  4. 單一繼承的虛函數
  5. 多重繼承的虛函數(需要調整this)
  6. 虛擬繼承的虛函數

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;}
};

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/83455.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/83455.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/83455.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

從Docker拉取鏡像一直失敗超時解決辦法

項目場景&#xff1a; 在ubuntu中&#xff0c;使用docker拉去鏡像時&#xff0c;一直超時&#xff0c;拉去失敗。 問題描述 原因分析&#xff1a; 國外服務器網絡不好導致。 解決方案&#xff1a; 解決方案1 設置國內源 我這邊測試&#xff0c;更改以后仍然失敗 阿里云提供…

KONG根據請求參數限流

背景 價格接口 /search 同時支持緩存查價和實時查價&#xff0c;主要通過searchType字段區分這兩種請求。 searchType 為空時為緩存查價&#xff0c;QPS很高。searchType 不為空時為實時查價&#xff0c;但QPS遠低于普通查價。 如果直接對該接口限流&#xff0c;當流量波動超…

通俗易懂解析:@ComponentScan 與 @MapperScan 的異同與用法

在 Spring 和 MyBatis 集成開發中&#xff0c;ComponentScan 和 MapperScan 是兩個核心注解&#xff0c;但它們的用途和工作機制截然不同。本文將通過通俗的語言和示例代碼&#xff0c;帶您輕松掌握它們的區別和使用方法。 一、基礎概念 ComponentScan&#xff1a;Spring 的“通…

39. 自動化異步測試開發之編寫異步業務函數、測試函數和測試類(函數寫法)

39. 自動化異步測試開發之編寫異步業務函數、測試函數和測試類&#xff08;函數寫法&#xff09; 一、異步業務函數解析 1.1 頁面導航函數 async def get(async_driver, url: str http://secure.smartbearsoftware.com/samples/testcomplete12/WebOrders/Login.aspx):await…

Qt 無邊框窗口實現拖動與窗口控制(最小化/最大化/關閉)

在 Qt 中&#xff0c;使用 Qt::FramelessWindowHint 可以創建無邊框窗口&#xff0c;但這樣會導致窗口無法拖動&#xff0c;并且系統默認的標題欄按鈕&#xff08;最小化、最大化、關閉&#xff09;也會消失。本文將介紹如何實現無邊框窗口的鼠標拖動功能&#xff0c;并添加自定…

Linux中的System V通信標準-共享內存、消息隊列以及信號量

在Linux系統中&#xff0c;System V IPC&#xff08;Inter-Process Communication&#xff09;提供了一系列進程間通信的機制&#xff0c;包括共享內存、消息隊列和信號量。這些機制在系統中發揮了重要作用&#xff0c;幫助進程之間進行數據交換和同步。本文將詳細介紹這些機制…

postman工具使用

基本功能操作 常用斷言 定義&#xff1a;postman 斷言借助 JavaScript - js 語言編寫代碼&#xff0c;自動判斷預期結果與實際結果是否一致。&#xff08; 注意斷言 代碼寫在 Tests 的標簽中&#xff09; 斷言響應狀態碼 斷言響應體是否包含某個字符串&#xff08;Response bo…

VBA數據庫解決方案二十:Select表達式From區域Where條件Order by

《VBA數據庫解決方案》教程&#xff08;版權10090845&#xff09;是我推出的第二套教程&#xff0c;目前已經是第二版修訂了。這套教程定位于中級&#xff0c;是學完字典后的另一個專題講解。數據庫是數據處理的利器&#xff0c;教程中詳細介紹了利用ADO連接ACCDB和EXCEL的方法…

算法-集合的使用

1、set常用操作 set<int> q; //以int型為例 默認按鍵值升序 set<int,greater<int>> p; //降序排列 int x; q.insert(x); //將x插入q中 q.erase(x); //刪除q中的x元素,返回0或1,0表示set中不存在x q.clear(); //清空q q.empty(); //判斷q是否為空&a…

C++文件和流基礎

C文件和流基礎 1. C文件和流基礎1.1 文件和流的概念1.2 標準庫支持1.3 常用文件流類ifstream 類ofstream 類fstream 類 2.1 打開文件使用構造函數打開文件使用 open() 成員函數打開文件打開文件的模式標志 2.2 關閉文件使用 close() 成員函數關閉文件關閉文件的重要性 3.1 寫入…

Maven---配置本地倉庫

目錄 5. 5.1在Maven路徑下新建文件夾用于本地倉庫存儲 5.2 復制本地倉庫路徑 5.3 找到配置文件路徑&#xff0c;使用VSCode方式打開 5.4 新增一行代碼 5.5 復制本地倉庫路徑&#xff0c;設置存儲路徑 5.1在Maven路徑下新建文件夾用于本地倉庫存儲 5.2 復制本地倉庫路徑 5…

Vue3 + Element Plus + TypeScript 中 el-cascader 實現模擬用戶點擊功能

模擬點擊&#xff0c;調用 el-cascader 的公開方法 togglePopperVisible 來展開下拉框 MaterialOut.vue <script setup lang"ts" name"MaterialOut"> ...... import { ElMessage, type ElCascader } from "element-plus";// 級聯組件實例…

新能源汽車與油車銷量

中國油車與新能源車銷量對比&#xff08;2022-2025年&#xff09; ?1. 市場份額演化&#xff08;2022-2025年&#xff09;? ?年份? ?新能源車銷量 &#xff08;滲透率&#xff09;? ?燃油車銷量 &#xff08;滲透率&#xff09;? ?關鍵事件? ?2022? 688.7萬輛…

C++ list代碼練習、set基礎概念、set對象創建、set大小操作

對應力扣&#xff0c;回文鏈表&#xff0c;代碼見下 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}* ListNode(int x, …

前端面試寶典---前端水印

明水印 1. 背景圖 通過css的background-image加載背景圖 2. canvasbackground水印 前端水印實現思路與示例代碼 一、核心實現思路 Canvas動態生成水印 通過Canvas繪制文本或圖案&#xff0c;將生成的圖像轉為Base64格式&#xff0c;作為背景圖重復平鋪到目標元素上。例如&…

惡意軟件清理工具,讓Mac電腦安全更簡單

?你的Mac最近是不是開始表演"電子迷惑行為"&#xff1f;瀏覽器主頁突然變成澳門賭場&#xff0c;風扇轉得比直升機螺旋槳還猛......恭喜你&#xff01;可能中獎獲得"惡意軟件大禮包"&#xff01;別慌&#xff0c;今天就教你用惡意軟件清理工具化身數字特工…

Spring Boot 3.X 下Redis緩存的嘗試(二):自動注解實現自動化緩存操作

前言 上文我們做了在Spring Boot下對Redis的基本操作&#xff0c;如果頻繁對Redis進行操作而寫對應的方法顯示使用注釋更會更高效&#xff1b; 比如&#xff1a; 依之前操作對一個業務進行定入緩存需要把數據拉取到后再定入&#xff1b; 而今天我們可以通過注釋的方式不需要額外…

Deepseek應用技巧-Dify安裝和踩坑指南

前言&#xff1a;Dify的名號是非常大的&#xff0c;作為私有化AI部署中必不可少的一個組件&#xff0c;他的功能和COZE十分相似&#xff0c;可以進行工作流和智能體的搭建&#xff0c;有非常強大的功能&#xff0c;那本節就將來揭開Dify的神秘的面紗&#xff0c;首先看一下Dify…

ubuntu24.04安裝教程(圖文詳解)

Ubuntu 24.04 LTS&#xff0c;代號 Noble Numbat&#xff0c;于 2024 年 4 月 25 日發布&#xff0c;現在可以從 Ubuntu 官方網站及其鏡像下載。此版本將在 2029 年 4 月之前接收為期五年的官方安全和維護更新。 關于 Ubuntu 24.04 LTS 的一些關鍵點&#xff1a; 發布日期&am…

數據綁定頁面的完整的原理、邏輯關系、實現路徑是什么?頁面、表格、字段、屬性、值、按鈕、事件、模型、腳本、服務編排、連接器等之間的關系又是什么?

目錄 一、核心概念:什么是數據綁定頁面? 二、涉及的組件及其邏輯關系 頁面(Page): 表格(Table): 字段(Field): 屬性(Property): 值(Value): 按鈕(Button): 事件(Event): 模型(Model): 腳本(Script): 服務(Service): 服務編排(Se…