說明:
? <u>不是很清楚的點(diǎn)</u>,用下劃線欧啤。
? 解答店印,用斜體;
? 重點(diǎn),用粗體加粗兰珍;
第五章 構(gòu)造掠河、析構(gòu)唠摹、拷貝 語意學(xué)
5.1 純虛函數(shù)
-
擁有純虛函數(shù)的類勾拉,為抽象類,不可能擁有實(shí)例(不可能創(chuàng)造出一個(gè)對象)。
但若抽象類中有數(shù)據(jù)成員惩激,則需要一個(gè)顯式的構(gòu)造函數(shù)去初始化它酒请。
不要把析構(gòu)函數(shù)聲明為 pure(純)布朦。
不要給一個(gè)虛函數(shù)后面加 const是趴。
5.2 “無繼承”情況下的對象構(gòu)造
當(dāng)一個(gè)class導(dǎo)入一個(gè)虛函數(shù)時(shí),會發(fā)生下列事情:
- 每一個(gè)class object多負(fù)擔(dān)一個(gè)vptr没佑;
- 自己定義的構(gòu)造函數(shù)被附加了一些代碼,實(shí)現(xiàn)vptr的初始化远剩;
- 合成拷貝構(gòu)造函數(shù)腹纳、賦值構(gòu)造函數(shù),因?yàn)関ptr不能用默認(rèn)的bitwise方式復(fù)制了;
5.3 繼承體系下的對象構(gòu)造
在繼承下象缀,編譯器會擴(kuò)充每一個(gè)constructor,擴(kuò)充程度視繼承體系而定爷速。
constructor的調(diào)用伴隨了哪些步驟央星?
初始化列表(member initialization list)的data members初始化操作會被放進(jìn)constructor的函數(shù)本身,并以members的聲明順序?yàn)轫樞?/strong>惫东。(如*this->x = 0;)
如果有一個(gè)member并沒有在初始化列表中莉给,但它在一個(gè)default constructor,那么該default constructor 必須被調(diào)用(手動)。
在那之前禁谦,如果class object有virtual table pointer(s)胁黑,它(們)必須被設(shè)定初始值,指定適當(dāng)?shù)膙irtual table(s)州泊。
-
在那之前,所有上一層的base class constructors 必須被調(diào)用漂洋,以base class 的聲明順序?yàn)轫樞颍ㄅc初始化列表的順序沒有關(guān)聯(lián))遥皂。
如果base class 被列于初始化列表中,那么任何明確指定參數(shù)都應(yīng)該傳遞過去刽漂。
如果base class 沒有列于初始化列表演训,那么調(diào)用default constructor。
如果base class 是多重繼承下的第二或后面的base class 贝咙,那么this指針必須有所調(diào)整样悟。
-
<u>在那之前,所有 virtual base class constructors 必須被調(diào)用庭猩,從左到右窟她,從最深到最淺。</u>
- 如果class 被列于初始化列表中蔼水,那么如果有任何明確指定的參數(shù)震糖,都應(yīng)該傳遞過去,若沒有列于初始化列表中趴腋,則調(diào)用default constructor吊说。
- 此外,class中的每一個(gè)virtual base class subobject的偏移量必須在執(zhí)行期可存取优炬。
- 如果class object 是最底層的class颁井,某constructors可能被調(diào)用;某些用以支持這個(gè)行為的機(jī)制必須被放進(jìn)來蠢护。
【注】在那之前雅宾,是指在用戶代碼執(zhí)行前。
其中的虛擬繼承
為了防止重復(fù)對virtual base class調(diào)用構(gòu)造函數(shù)糊余,規(guī)定:只有在繼承體系最深層的object才可以對virtual父類進(jìn)行調(diào)用構(gòu)造初始化秀又。
其中的vptr語意學(xué)
vptr在constructor何時(shí)被初始化?
在base class constructors調(diào)用操作之后贬芥,但是在程序員供應(yīng)的碼或是初始化列表中所列的members初始化操作之前吐辙。
5.4 對象復(fù)制語意學(xué)
在復(fù)制操作時(shí),需要一個(gè)面對自我拷貝的過濾過程:
if( this == &rhs) return *this;
當(dāng)設(shè)計(jì)一個(gè)class蘸劈,并以一個(gè)class object 指定另一個(gè)class object時(shí)昏苏,有三種選擇:
什么都不做,實(shí)施默認(rèn)行為。
提供一個(gè)explicit copy assignment operator贤惯。
明確拒絕一個(gè)class object指定給另一個(gè)class object洼专。
一個(gè)class對于默認(rèn)的copy assignment operator,在以下情況下不會表現(xiàn)出 bitwise copy語意:
當(dāng)一個(gè)class的 base class 有一個(gè)copy assignment operator時(shí)孵构,
當(dāng)一個(gè)class 的 member object屁商,而其 class 有一個(gè) copy assignment operator 時(shí),
當(dāng)一個(gè)class 聲明了任何 virtual functions 時(shí)颈墅,
當(dāng)class繼承一個(gè) virtual base class 時(shí)蜡镶。但盡可能不要允許一個(gè)virtual base class的拷貝操作,也盡量不要在其中聲明數(shù)據(jù)恤筛。
構(gòu)造這樣的一個(gè)繼承體系:
class Base {
public: virtual ~Base() {}
virtual void show() { cout << "Base" << endl; }
};
class Derived : public Base {
public: void show() { cout << "Derived" << endl; }
};
子類Derived類重寫了基類Base中的show方法官还。 編寫下面的測試代碼:
Base b;
Derived d;
b.show();
d.show();
結(jié)果是:
Base
Derived
Base的對象調(diào)用了Base的方法,而Derived的對象調(diào)用了Derived的方法毒坛。因?yàn)橹苯佑脤ο髞碚{(diào)用成員函數(shù)時(shí)不會開啟多態(tài)機(jī)制望伦,故編譯器直接根據(jù)b和d各自的類型就可以確定調(diào)用哪個(gè)show函數(shù)了,也就是在這兩句調(diào)用中煎殷,編譯器為它們每一個(gè)都確定了一個(gè)唯一的入口地址屯伞。這實(shí)際上類似于一個(gè)重載多態(tài),雖然這兩個(gè)show函數(shù)擁有不同的作用域蝌数。 那這樣呢: Base b; Derived d; b.show(); b = d; b.show(); 現(xiàn)在愕掏,一個(gè)Base的對象被賦值為子類Derived的對象。
那這樣呢:
Base b;
Derived d;
b.show();
b = d;
b.show();
現(xiàn)在顶伞,一個(gè)Base的對象被賦值為子類Derived的對象饵撑。
結(jié)果是:
Base
Base
對于熟悉Java的人而言,這不可理解唆貌。但實(shí)際上滑潘,C++不是Java,它更像C锨咙∮锫保“b = d”的意思,并不是Java中的“讓一個(gè)指向Base類的引用指向它的子類對象”酪刀,而是“把Base類的子類對象中的Base子對象分割出來粹舵,賦值給b”。所以骂倘,只要b的類型始終是Base眼滤,那么b.show()調(diào)用的永遠(yuǎn)都是Base類中的show函數(shù);換句話說历涝,編譯器總是把Base中的那個(gè)show函數(shù)的入口地址作為b.show()的入口地址诅需。這根本就沒用上多態(tài)漾唉。
單繼承下的重寫多態(tài)
那我們再這樣:
Base b;
Derived d;
Base *p = &b;
p->show();
p = &d;
p->show();
這時(shí),結(jié)果就對了:
Base
Derived
p是一個(gè)指向基類對象的指針堰塌,第一次它指向一個(gè)Base對象赵刑,p->show()調(diào)用了Base類的show函數(shù);而第二次它指向了一個(gè)Derived對象场刑,p->show()調(diào)用了Derived類的show函數(shù)般此。
總結(jié):也就是說,只有是指針或者引用才是真正的多態(tài)牵现,將子對象賦給父類對象其實(shí)類型向上轉(zhuǎn)型
個(gè)人覺得C++容易弄混淆的地方(持續(xù)更新):
1.const和指針的修飾問題
const char * a; //一個(gè)指針a指向const char
char const *a; //這兩個(gè)是a指向的內(nèi)容是常量恤煞,不能改變
char * const a; //首先a 是指針然后還是const
const (char*) a; //這兩個(gè)是a指針本身是常量,指針本身不能改變
其實(shí)施籍,可以看出如果const修飾的char(也就是類型本身或者是 *variable對指針的解引用)就是指針指向的內(nèi)容是常量,反之就是修飾指針本身的概漱。那我們可以總結(jié)一個(gè)識別方法就是:看const 兩邊(當(dāng)然有的只有一邊)的類型是類型(指針指向的內(nèi)容)就是類型變量本身是常量(如const char * a和char const a 的const兩邊是char丑慎,a)。
當(dāng)然兩者都是常量就是:const char * const a;第一個(gè)const是類型常量瓤摧,第二個(gè)才是指針常量竿裂。同樣給出 const char &a ;const char *a;在傳遞參數(shù)時(shí)使用。
2.數(shù)組和指針的組合問題
char * a[M]; 這是指針數(shù)組照弥,就是每一個(gè)元素是指針的數(shù)組腻异,每個(gè)元素都要初始化。a[M]一看就是數(shù)組这揣,這個(gè)數(shù)組每一個(gè)元素是char *悔常,所以可以將char *擴(kuò)展為一維數(shù)組然后a[M]就是二維數(shù)組了。其實(shí)就是M個(gè)指針给赞。
char (a)[N]; 這是一個(gè)指針机打,這個(gè)指針指向N個(gè)char元素,即指向數(shù)組的指針片迅,其實(shí)就是一個(gè)指針残邀。把(a)看著一個(gè)變量,這個(gè)變量是指向N個(gè)元素的指針柑蛇,所以只是一個(gè)一維數(shù)組芥挣。把char (*a)[N]看成是char b[N]就可以了。
3.C++變量的初始化
對于內(nèi)置類型局部變量不進(jìn)行初始化耻台,但是分配地址空免,全局變量會進(jìn)行默認(rèn)初始化。對于類類型局部變量(沒有顯式初始化)會進(jìn)行默認(rèn)初始化(有默認(rèn)構(gòu)造函數(shù)粘我,否則報(bào)錯(cuò))鼓蜒,但其內(nèi)部的內(nèi)置數(shù)據(jù)成員不會進(jìn)行初始化(如果在默認(rèn)構(gòu)造函數(shù)沒有進(jìn)行初始化)痹换。數(shù)組也是同樣。
參考文章
《深度探索C++對象模型(Inside The C++ Object Model )》學(xué)習(xí)筆記:https://dsqiu.iteye.com/blog/1669614
c++都弹,為什么要引入虛擬繼承 :https://www.cnblogs.com/mylinux/p/4725833.html
RTTI娇豫、虛函數(shù)和虛基類的實(shí)現(xiàn)方式、開銷分析及使用指導(dǎo):http://baiy.cn/doc/cpp/inside_rtti.htm