閱讀《深度探索 C++ 對象模型》第一章雖然仍有疑惑,但收穫頗豐。根據經驗,第一章屬於概覽性介紹,具體細節留待後續章節闡述。若未通讀全書,單看第一章確實較難理解。第一章讓我從最初的「世界末日」感中走出,信心倍增,也摸索出一些門徑。繼續踏上深度探索 C++ 對象模型的旅程,記住「堅持不懈,直到成功」是通往成功的不二法門。
第二章探討構造函數語意。筆者認為「Semantics」譯為「語義」較為恰當:語義學研究語言形式表達意義的科學。本章旨在以語言形式清晰描述構造函數的工作機制。
構造函數是類的成員函數,與類同名,無返回值(連 void 也不能有)。主要負責對象數據的創建、初始化;析構函數負責清理。構造函數可重載,析構函數不可重載。重載區別僅在參數列表(個數或類型)。
本章不講構造函數是什麼,而是講它如何構造對象。這正是筆者一直好奇的:為何類實例化時能自動完成諸多工作,使主函數清晰、簡單、穩健、高效?書中重點討論編譯器合成預設構造函數的幾種情況。
若類無任何構造函數,但擁有成員對象,且該成員對象的類有預設構造函數,編譯器會在必要時為該類合成一個構造函數(內聯方式)。
// 範例類別
class Cat { public: Cat(); ... };
class Dog { public: Dog(); ... };
class Bird { public: Bird(); ... };
class Fish { public: Fish(); ... };
class Pet {
public:
Cat aCat;
Dog aDog;
Bird aBird;
Fish aFish;
private:
int ival;
...
};
編譯器合成的構造函數大致如下:
inline Pet::Pet() {
aCat.Cat::Cat();
aDog.Dog::Dog();
aBird.Bird::Bird();
aFish.Fish::Fish();
ival = 0;
}
若程式設計師自行定義 Pet::Pet() { ival = 0; },編譯器仍會將其擴展為上述形式。編譯器在生成對象前,先調用必要的構造函數初始化成員,以分配適當記憶體空間。
若你惹上官司(設計類),卻無錢聘請高級律師(未提供構造函數),法院會指派律師(預設構造函數)。這律師能力或許不如大律師(trivial),但在律師行督導下(編譯器合成),能盡力做到最好,成為 nontrivial default constructor。
合成的預設構造函數僅初始化基類子對象與類的成員對象;其他非靜態數據成員不會被初始化,這些工作留給程式設計師完成。
侯大師曾說:「練從難處練,用從易處用。知其然而不知其所以然,不是嚴謹的學習態度。」深入理解編譯器合成機制,有助於寫出更健壯、高效的 C++ 程式碼。