C++對象布局及多態(tài)之虛成員函數(shù)調(diào)用

字號:

在構(gòu)造函數(shù)中調(diào)用虛成員函數(shù),雖然這是個不很常用的技術(shù),但研究一下可以加深對虛函數(shù)機(jī)制及對象構(gòu)造過程的理解。這個問題也和一般直觀上的認(rèn)識有所差異。先看看下面的兩個類定義。
    struct C180
    {
    C180() {
    foo();
    this->foo();
    }
    virtual foo() {
    cout << "<< C180.foo this: " << this << " vtadr: " << *(void**)this << endl;
    }
    };
    struct C190 : public C180
    {
    C190() {}
    virtual foo() {
    cout << "<< C190.foo this: " << this << " vtadr: " << *(void**)this << endl;
    }
    };
    父類中有一個虛函數(shù),并且父類在它的構(gòu)造函數(shù)中調(diào)用了這個虛函數(shù),調(diào)用時它采用了兩種方法一種是直接調(diào)用,一種是通過this指針調(diào)用。同時子類又重寫了這個虛函數(shù)。
    我們可以來預(yù)測一下如果構(gòu)造一個C190的對象會發(fā)生什么情況。
    我們知道,在構(gòu)造一個對象時,首先會按對象的大小得到一塊內(nèi)存(在heap上或在stack上),然后會把指向這塊內(nèi)存的指針做為this指針來調(diào)用類的構(gòu)造函數(shù),對這塊內(nèi)存進(jìn)行初始化。如果對象有父類就會先調(diào)用父類的構(gòu)造函數(shù)(并依次遞歸),如果有多個父類(多重繼承)會依次對父類的構(gòu)造函數(shù)進(jìn)行調(diào)用,并會適當(dāng)?shù)恼{(diào)整this指針的位置。在調(diào)用完所有的父類的構(gòu)造函數(shù)后,再執(zhí)行自己的代碼。
    照上面的分析構(gòu)造C190時也會調(diào)用C180的構(gòu)造函數(shù),這時在C180構(gòu)造函數(shù)中的第一個foo調(diào)用為靜態(tài)綁定,會調(diào)用到C180::foo()函數(shù)。第二個foo調(diào)用是通過指針調(diào)用的,這時多態(tài)行為會發(fā)生,應(yīng)該調(diào)用的是C190::foo()函數(shù)。
    執(zhí)行如下代碼:
    C190 obj;
    obj.foo();
    結(jié)果為:
    << C180.foo this: 0012F7A4 vtadr: 0045C404
    << C180.foo this: 0012F7A4 vtadr: 0045C404
    << C190.foo this: 0012F7A4 vtadr: 0045C400
    和我們的分析大相徑庭。前2行是構(gòu)造C190時的輸出,后1行是我們用靜態(tài)綁定方式調(diào)用的C190::foo()函數(shù)。第2行的輸出說明多態(tài)行為并沒有象預(yù)期的那樣發(fā)生。而且比較輸出的最后一列,發(fā)現(xiàn)在調(diào)用C180的構(gòu)造函數(shù)時對象對應(yīng)的虛表和構(gòu)造后對象對應(yīng)的虛表不是同一個。其實(shí)這正是奧秘的所在。
    為此我查了一下C++標(biāo)準(zhǔn)規(guī)范。在12.7.3條中有明確的規(guī)定。這是一種特例,在這種情況下,即在構(gòu)造子類時調(diào)用父類的構(gòu)造函數(shù),而父類的構(gòu)造函數(shù)中又調(diào)用了虛成員函數(shù),這個虛成員函數(shù)即使被子類重寫,也不允許發(fā)生多態(tài)的行為。即,這時必須要調(diào)用父類的虛函數(shù),而不子類重寫后的虛函數(shù)。
    我想這樣做的原因是因為在調(diào)用父類的構(gòu)造函數(shù)時,對象中屬于子類部分的成員變量是肯定還沒有初始化的,因為子類構(gòu)造函數(shù)中的代碼還沒有被執(zhí)行。如果這時允許多態(tài)的行為,即通過父類的構(gòu)造函數(shù)調(diào)用到了子類的虛函數(shù),而這個虛函數(shù)要訪問屬于子類的數(shù)據(jù)成員時就有可能出錯。