考慮以下代碼
class base {
public:
base() :num(0){
log();
}
virtual void log() {
num++;
numsta++;
}
int num;
static int numsta;
};
int base::numsta = 0;
class derived1:public base {
public:
derived1() : num(0) {
}
virtual void log() {
num++;
}
void fun() {
cout << num<<endl;
}
int num;
};
class derived2:public base {
public:
derived2() : num(0) {
}
virtual void log() {
num++;
}
void fun() {
cout << num<<endl;
}
int num;
};
int main(){
derived1* d = new derived1;
derived2* e = new derived2;
base* p = dynamic_cast<base*>(d);
base* q = dynamic_cast<base*>(e);
d->fun();
e->fun();
cout << p->num<<endl;
cout << q->num << endl;
cout << base::numsta;
return 0;
}
最終的輸出是

將log()函數(shù)放入構(gòu)造函數(shù),本意是要記錄對應(yīng)的子類對象的個數(shù)。
但是我們可以看到前兩個輸出,也就是說兩個子類中的num值并沒有被修改。
再看numsta的值被修改為2,也就是說構(gòu)造函數(shù)內(nèi)調(diào)用的log()是基類的版本。
實際上,基類構(gòu)造期間虛函數(shù)絕不會下降到子類階層,有種說法比較傳神:在基類構(gòu)造期間,虛函數(shù)并不是虛函數(shù)。
也可以換一個角度理解:構(gòu)造子類對象時,首先會調(diào)用基類構(gòu)造函數(shù),如果此時調(diào)用的虛函數(shù)下降到子類階層,并且子類虛函數(shù)調(diào)用了子類的成員變量,然而這些變量其實并還沒有初始化!,所以C++不會讓我們這樣做。
實際上如果在基類構(gòu)造期間,使用dynamic_cast,你會發(fā)現(xiàn)這時候這個對象是基類類型的。
同樣,在析構(gòu)過程中,一旦執(zhí)行到了基類的析構(gòu)函數(shù),那么這個對象退化成了基類對象。
回頭看第三行和第四行的輸出,這兩個子類對象中,它們的基類部分里num的值都被修改為1,這是符合以上分析的。
但是有時候可能會不小心忽略了以上警告
base(){
init();
}
void init(){
log();
}
這樣的代碼可能會讓你不小心進入以上陷阱,所以在這一點上務(wù)必要小心。
有一種比較好的解決方法
就是將log函數(shù)定義為非虛函數(shù),并且子類構(gòu)造函數(shù)的初始化列表中使用基類名的形式,如
derived(): base(logString){}
然后讓基類的構(gòu)造函數(shù)接收這個logString,然后在基類構(gòu)造函數(shù)內(nèi)根據(jù)子類構(gòu)造函數(shù)傳來的參數(shù)來執(zhí)行存儲日志的操作。
也就是用這種方法,要記錄各個子類對象的個數(shù),存儲的變量就不能放在子類中了(如果是普通變量),可以聲明為static變量,或者使用全局變量。