有個常見的迷思是:
- 如果我們沒有為一個class寫default constructor,則compiler會幫我們做一個出來。
- 這個default constructor會幫我們把data member給初始值
但事實上,這兩點都不正確,甚至包括destructor、copy constructor、copy assignment operator也不一定會有。
回到問題的原點,為什麼compiler會需要幫我們做一個default constructor? 答案是compiler自己需要,要了解為什麼compiler會有此需求,我們得了解一點compiler到底對我們的程式做了什麼手腳,下面的C++ code:
class X { int _data; public: void showData() { cout << _data; } }; int main() { X x; x.showData(); }會被compiler變成下面的樣子,然後才編譯成機器語言
void __X_showData(X* pX) { cout << pX->_data; } int main() { X x; __X_showData(&x); }是否有回到C語言的感覺,class X不見了,取而代之的是一個global function,而main()當中的local變數x則會導致compiler幫我們在stack中配置一個大小為4 bytes的位置給X用來存放X的唯一data member: data,然後在呼叫__X_showData時把這個位置以指標的形式傳進去。
這種情況下compiler是不會幫我們做出default constructor的,因為不需要。那什麼情況下會需要default constructor?
class String { int _size; char* pStr; public: String() { _size = 0; _pStr = 0; } }; class Person { String _name; int _age; public: void showName() { cout << _name; } }; int main() { Person p; p.showName(); }由於String有宣告default constructor,C++語言標準要求在產生Person物件時,它的的data member: String name的default constructor必須被呼叫到,因此C++ compiler為了符合C++標準的規範,必須幫Person class做了一個default constructor,其唯一的任務就是呼叫name的default constructor:
inline void __String_String(String* pThis) { pThis->_size = 0; pThis->_pPtr = 0; } inline void __Person_Person(Person* pThis) { __String_String(pThis->_name); } void __Person_showName(Person* pThis) { cout << pThis->_name; } int main() { Person p; __Person_Person(&p); __Person_showName(&p); }值得注意的是:
- Compiler合成的default constructor只做它需要的事(呼叫String name的default constructor),它不會幫我們初始化像int _age這種data member,因此_age的值不會被清為0
- Compiler合成的default constructor會盡可能宣告成inline,因此即使Person物件位於bottleneck處,也不會因為做了default constructor而付出function call overhead的代價。
class Shape { public: virtual void draw() {...} }; class Circle : public Shape{ int x; int y; int radius; public: virtual void draw() {...} }; int main() { Circle c; Shape* pShape = &c; pShape->draw(); // Circle::draw() been called, not Shape::draw() }以下是C++ compiler做的手腳:
void __Shape_draw(Shape* pShape) {...} void* __vftable_Shape[] = { __Shape_draw }; void __Shape_Shape(Shape* pThis) { pThis->__vfPtr = __vftable_Shape; } void __Circle_draw(Circle* pThis) {...} viud* __vftable_Circle[] = { __Circle_draw }; viud __Circle_Circle(Circle* pThis) { pThis->__vfPtr = __vftable_Circle; } int main() { Circle c; __Circle_Circle(&c); // call Circle's constructor to setup Circle's __vfPtr Shape* pShape = &c; pShape->__vfPtr[0] (pShape); }關於virtual function,C++ compiler標準的做法是:
- 產生一份virtual table(__vftable_XXX),其中每個slot是指向virtual function的指標
- 為每個物件安插一個指標(__vfPtr),指向virtual table,並在constructor中安插程式碼幫__vfPtr初始化,指到該class的virtual table
然後考慮下面的code
Circle c Shape s1(c); s1.draw(); Shape s2 = c; s2.draw();如果compiler沒有合成copy constructor或copy assignment operator,則會以bitwise copy的方式來產生s1, s2
Circle c; Shape s1.__vfPtr = c.__vfPtr; s1.__vfPtr[0](); // Circle::draw() been called, which is wrong! Shape s2.__vfPtr = c.__vfPtr; s1.__vfPtr[0](); // Circle::draw() been called, which is wrong!上面的錯誤在於s1.draw()應該叫到Shape::draw(),但由於s1在產生的時候,不小心複製到Circle的virtual function pointer,而Circle的virtual table中第一個欄位當然指向Circle::draw()而不是Shape::draw()。因此compiler需要產生copy constructor和copy assignment operator來幫s1, s2設定正確的__vfPtr:
inline void __Shape_Shape(Shape* pThis, const Shape& rhs) { pThis->__vfPtr = __vftable_Shape; } inline void __Shape_operator::=(Shape* pThis, const Shape& rhs) { pThis->__vfPtr = __vftable_Shape; }
事實上以support virtual function來看就更清楚知道為什麼compiler需要幫class產生default constructor (為了設定__vfPtr),即使我們有寫default constructor,compiler也會安插程式碼進去設定__vfPtr。
關於C++物件模型的書,我會推薦Stan Lipperman's Inside The C++ Object Model,有中譯本由侯俊傑翻譯,值得一讀!
大開眼界~!
回覆刪除嗚~ 書單塞車快比過年的車潮還塞了.... QQ
回覆刪除