有個常見的迷思是:
- 如果我們沒有為一個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
回覆刪除