2012年春節深夜,鞭炮聲中研讀《Inside C++ Object Model》(侯捷譯),對C++對象佈局的精妙設計驚嘆不已。書中修正了原版諸多錯誤,不愧為經典。記得《Effective C++》條款7提到:「只有類含虛函數時才聲明虛析構函數」,當時僅知虛函數表存在,如今窺見全貌,方知其深意。—— Stay hungry, stay foolish!
無虛函數時類似C struct;有虛函數則引入vptr指向虛函數表。單一繼承下,派生類並非單純複製基類成員,而是以基類子對象形式佈局,遵循內存對齊原則。例如:
class BaseClass { protected: int m_nValue; char m_chValue; };
class DerivedClass1 : public BaseClass { protected: char m_chValue1; };
class DerivedClass2 : public DerivedClass1 {
// virtual void VFunction(){}
protected: char m_chValue2;
};
VS2008下 sizeof(DerivedClass2) == 16。此佈局等價於包含委托對象:
class DerivedClass2 {
// virtual void VFunction(){}
protected: DerivedClass1 derived1; char m_chValue2;
};
class BaseClass {};
class DerivedClassA : virtual public BaseClass {
public: /*virtual void VFunction(){}*/
};
class DerivedClassB : virtual public BaseClass {};
class DerivedClass : public DerivedClassA, public DerivedClassB {};
VS2008結果:
size of BaseClass : 1 // 空類佔1字節確保地址唯一 size of DerivedClassA : 4 // 含vbptr指向虛基類表 size of DerivedClassB : 4 // 含vbptr size of DerivedClass : 8 // 兩個vbptr(VS未優化合併)
G++優化合併同類型vbptr,結果為1,4,4,4。
DerivedClassA加虛函數 → 增加vfptr,大小變8;DerivedClass變12。BaseClass → 基類變4(含vfptr),兩派生類各增一個繼承來的vfptr變8,最派生類12(而非預期16,編譯器合併了同類型指針)。| 指針類型 | 指向 | 觸發條件 |
|---|---|---|
vfptr | 虛函數表(虛函數地址) | 類聲明虛函數 |
vbptr | 虛基類表(虛基類子對象地址) | 虛繼承基類 |
兩者初始化/析構均視為普通非靜態數據成員處理。
Scott Meyers警告濫用虛函數會無形增加內存開銷。Lippman對對象模型的微觀剖析,讓我意識到:理解佈局不是為了炫技,而是為了在資源受限、性能敏感的系統中做精準決策。寫博客記錄,是為了不忘初心,更為下次踩坑時能秒懂。
本文出自「酋長」博客,轉載請保留出處。