Effective C++面向?qū)ο笈c繼承
1:子類不要覆寫父類的非虛函數(shù)。
2:子類不要覆寫從父類繼承過來的默認參數(shù)
3:子類與父類之間的賦值問題
1:子類不要覆寫父類的非虛函數(shù)。
為了解釋方便,先看一個簡單的例子。
class A { public: A(int d):data(d){ } void print() { cout<<"A print..."<<data<<endl; } virtual void test(int i=2) { cout<<"A test..."<<i<<endl; } private: int data; }; class B:public A { public : B(int d):A(d){ } void print() { cout<<"B print..."<<endl; } virtual void test(int i=4) { cout<<"B test..."<<i<<endl; } }; //測試代碼 int main() { { B b(5); b.print(); A *a=&b; a->print(); cout<<endl; b.test(); a->test(); cout<<endl; A a1=b; a1.test(); } getchar(); return 0; }
運行結(jié)果截圖:

例子中指針a是指向?qū)ο骲的,但是他們調(diào)用的print方法卻不是同一個。這里涉及到靜態(tài)綁定和動態(tài)綁定的問題。a的靜態(tài)類型是A,a的動態(tài)的類型卻是B,b的靜態(tài)類型和動態(tài)類型都是B,因為靜態(tài)類型就是申明時的類型,動態(tài)類型是其真正指向的類型。還有一點就是非虛方法是靜態(tài)綁定,虛擬方法是動態(tài)綁定。Print是非虛方法,它是靜態(tài)綁定,調(diào)用的是自己的對象申明類型的方法,所以a調(diào)用的是A的print,b調(diào)用的是B的print方法。我想我們更想知道C++是怎么實現(xiàn)動態(tài)綁定。我們都知道含有虛方法的類都有一個虛擬方法表,每個對象的實例都有一個指針指向這個虛擬方法表,子類會繼承父類的virtual方法,也可以覆寫父類的虛擬方法,如果子類覆寫父類的虛擬方法,那么在虛擬表中對應(yīng)的指針就指向子類覆寫父類的方法,如果子類不覆寫父類的虛擬方法,則還是指向父類的方法,這樣就形成了動態(tài)綁定。不同的子類按照自己的方式覆寫父類的虛擬方法,表現(xiàn)出不同的行為這就是多態(tài)。在多重繼承中,每個對象可能有多個虛擬表,那么它的實例就會有多個指向虛擬表的指針,如果多個父類有一個相同的方法,那么你就不能直接用這個實例調(diào)用這個方法,因為編譯器根本不知道它該調(diào)用哪個方法,你要指定是那個父類的方法,當(dāng)你指明了哪個父類,編譯就可以通過對應(yīng)的指針調(diào)用對應(yīng)的虛擬表中對應(yīng)的方法。那么實例調(diào)用虛擬方法的過程是怎么樣的呢,你有沒有想過?其實上面也提到一點,大致三步:
1:根據(jù)對象的vptr指針找到其虛擬方法表vtbl;
2:找到被調(diào)用方法在vtbl中對應(yīng)的指針;
3:調(diào)用2中指針指向的方法。
2:子類不要覆寫從父類繼承過來的默認參數(shù)
這一條其實還是涉及到靜態(tài)綁定和動態(tài)綁定的問題,關(guān)于這個問題我想上面已經(jīng)說得比較清楚了,默認值也是靜態(tài)綁定,這是毫無疑問的,因為它在編譯期就已經(jīng)確定了,而虛擬方法確實動態(tài)綁定,你把靜態(tài)綁定的東西和動態(tài)綁定的東西攪在一起沒有問題,但是你還有得寸進尺的在子類中覆寫靜態(tài)的東西就會出問題,對不起,父類不管子類中靜態(tài)的東西,它只管自己靜態(tài)的東西,所以當(dāng)子類不要覆寫從父類繼承過來的默認參數(shù)時,子類就可能出現(xiàn)精神分裂的行為,上面那個列子就是證明。
上面更多提到的都是關(guān)于虛擬方法的,那么非虛擬方法呢,對象實例時怎么調(diào)用非虛擬方法的呢?非虛擬方法是怎么實現(xiàn)的呢?非虛擬方法就像一般的C函數(shù)那樣被實現(xiàn)的,所以他們的調(diào)用不需要像虛擬方法一樣先要找到一個指針,然后在通過這個指針調(diào)用對應(yīng)的方法。
3:子類與父類之間的賦值問題
首先將父類轉(zhuǎn)換成子類的事最好不要做,因為子類的很多特性父類根本沒有,當(dāng)你把一個從父類轉(zhuǎn)換過來的子類,當(dāng)做子類來用的話,很可能出問題。接下來我們重點討論將子類轉(zhuǎn)換成父類。還是通過上面例子來說明問題。
B b(2);
A a=b;//調(diào)用copy constructor
a=b;//調(diào)用 operator=
上面兩行代碼,第一行先實例化了一個對象b,第二行將b賦給a,那么是怎么將b賦給a的呢,這里其實調(diào)用的不是operator=,而是copy constructor,因為構(gòu)造一個對象必須調(diào)用constructor,或是copy constructor,那么這里肯定是調(diào)用copy constructor,operator=只是一個賦值動作,一個對象還沒有構(gòu)造出來怎么給他賦值呢,在operator=可不是用來幫你構(gòu)造對象的哦,在第三行的時候a已經(jīng)被構(gòu)造出來了,那么這里真的就是賦值了調(diào)用的就是operator=。總之一句話,一個對象作為左值時,第一次肯定調(diào)用的是copy constructor,被初始化后(分配了內(nèi)存),之后的操作才是賦值。一個對象作為by value形式的參數(shù),那么每次調(diào)用的都是copy constructor,而不是operator=,我們一般都會說將實參賦給形參,其實是用實參構(gòu)造一個形參。
將b賦給a,就是將b的A部分賦給a,a就是一個完全的A了,它對B一無所知,更不會表現(xiàn)出B的任何行為,所以by value是很暴力并且很耗性能的,也不會出現(xiàn)多態(tài)的行為。所以要避免使用by value,盡量用by reference。
就此打住,未完待續(xù)...
Effective C++系列:
Effective C++構(gòu)造函數(shù)析構(gòu)函數(shù)Assignment運算符
Effective C++ 類與函數(shù)的設(shè)計和申明
作者:陳太漢
浙公網(wǎng)安備 33010602011771號