深度探索C++對(duì)象模型-第五章

說(shuō)明:

? <u>不是很清楚的點(diǎn)</u>,用下劃線。

? 解答,用斜體;

? 重點(diǎn),用粗體加粗;

第五章 構(gòu)造、析構(gòu)、拷貝 語(yǔ)意學(xué)

5.1 純虛函數(shù)

  • 擁有純虛函數(shù)的類(lèi),為抽象類(lèi),不可能擁有實(shí)例(不可能創(chuàng)造出一個(gè)對(duì)象)。

    但若抽象類(lèi)中有數(shù)據(jù)成員,則需要一個(gè)顯式的構(gòu)造函數(shù)去初始化它。

  • 不要把析構(gòu)函數(shù)聲明為 pure(純)。

  • 不要給一個(gè)虛函數(shù)后面加 const。

5.2 “無(wú)繼承”情況下的對(duì)象構(gòu)造

當(dāng)一個(gè)class導(dǎo)入一個(gè)虛函數(shù)時(shí),會(huì)發(fā)生下列事情:

  1. 每一個(gè)class object多負(fù)擔(dān)一個(gè)vptr;
  2. 自己定義的構(gòu)造函數(shù)被附加了一些代碼,實(shí)現(xiàn)vptr的初始化;
  3. 合成拷貝構(gòu)造函數(shù)、賦值構(gòu)造函數(shù),因?yàn)関ptr不能用默認(rèn)的bitwise方式復(fù)制了;

5.3 繼承體系下的對(duì)象構(gòu)造

在繼承下,編譯器會(huì)擴(kuò)充每一個(gè)constructor,擴(kuò)充程度視繼承體系而定。

constructor的調(diào)用伴隨了哪些步驟?

  • 初始化列表(member initialization list)的data members初始化操作會(huì)被放進(jìn)constructor的函數(shù)本身,并以members的聲明順序?yàn)轫樞?/strong>。(如*this->x = 0;)

  • 如果有一個(gè)member并沒(méi)有在初始化列表中,但它在一個(gè)default constructor,那么該default constructor 必須被調(diào)用(手動(dòng))。

  • 在那之前,如果class object有virtual table pointer(s),它(們)必須被設(shè)定初始值,指定適當(dāng)?shù)膙irtual table(s)。

  • 在那之前,所有上一層的base class constructors 必須被調(diào)用,以base class 的聲明順序?yàn)轫樞颍ㄅc初始化列表的順序沒(méi)有關(guān)聯(lián))。

    1. 如果base class 被列于初始化列表中,那么任何明確指定參數(shù)都應(yīng)該傳遞過(guò)去。

    2. 如果base class 沒(méi)有列于初始化列表,那么調(diào)用default constructor。

    3. 如果base class 是多重繼承下的第二或后面的base class ,那么this指針必須有所調(diào)整。

  • <u>在那之前,所有 virtual base class constructors 必須被調(diào)用,從左到右,從最深到最淺。</u>

    1. 如果class 被列于初始化列表中,那么如果有任何明確指定的參數(shù),都應(yīng)該傳遞過(guò)去,若沒(méi)有列于初始化列表中,則調(diào)用default constructor。
    2. 此外,class中的每一個(gè)virtual base class subobject的偏移量必須在執(zhí)行期可存取。
    3. 如果class object 是最底層的class,某constructors可能被調(diào)用;某些用以支持這個(gè)行為的機(jī)制必須被放進(jìn)來(lái)。

【注】在那之前,是指在用戶(hù)代碼執(zhí)行前。

其中的虛擬繼承

為了防止重復(fù)對(duì)virtual base class調(diào)用構(gòu)造函數(shù),規(guī)定:只有在繼承體系最深層的object才可以對(duì)virtual父類(lèi)進(jìn)行調(diào)用構(gòu)造初始化。

其中的vptr語(yǔ)意學(xué)

vptr在constructor何時(shí)被初始化?

base class constructors調(diào)用操作之后,但是在程序員供應(yīng)的碼或是初始化列表中所列的members初始化操作之前。

5.4 對(duì)象復(fù)制語(yǔ)意學(xué)

在復(fù)制操作時(shí),需要一個(gè)面對(duì)自我拷貝的過(guò)濾過(guò)程:

if( this == &rhs) return *this;

當(dāng)設(shè)計(jì)一個(gè)class,并以一個(gè)class object 指定另一個(gè)class object時(shí),有三種選擇:

  1. 什么都不做,實(shí)施默認(rèn)行為。

  2. 提供一個(gè)explicit copy assignment operator。

  3. 明確拒絕一個(gè)class object指定給另一個(gè)class object。

一個(gè)class對(duì)于默認(rèn)的copy assignment operator,在以下情況下不會(huì)表現(xiàn)出 bitwise copy語(yǔ)意:

  1. 當(dāng)一個(gè)class的 base class 有一個(gè)copy assignment operator時(shí),

  2. 當(dāng)一個(gè)class 的 member object,而其 class 有一個(gè) copy assignment operator 時(shí),

  3. 當(dāng)一個(gè)class 聲明了任何 virtual functions 時(shí),

  4. 當(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; }
};

子類(lèi)Derived類(lèi)重寫(xiě)了基類(lèi)Base中的show方法。 編寫(xiě)下面的測(cè)試代碼:

Base b; 
Derived d;

b.show(); 
d.show();

結(jié)果是:

Base

Derived

Base的對(duì)象調(diào)用了Base的方法,而Derived的對(duì)象調(diào)用了Derived的方法。因?yàn)橹苯佑脤?duì)象來(lái)調(diào)用成員函數(shù)時(shí)不會(huì)開(kāi)啟多態(tài)機(jī)制,故編譯器直接根據(jù)b和d各自的類(lèi)型就可以確定調(diào)用哪個(gè)show函數(shù)了,也就是在這兩句調(diào)用中,編譯器為它們每一個(gè)都確定了一個(gè)唯一的入口地址。這實(shí)際上類(lèi)似于一個(gè)重載多態(tài),雖然這兩個(gè)show函數(shù)擁有不同的作用域。 那這樣呢: Base b; Derived d; b.show(); b = d; b.show(); 現(xiàn)在,一個(gè)Base的對(duì)象被賦值為子類(lèi)Derived的對(duì)象。

那這樣呢:

Base b; 
Derived d;

b.show(); 
b = d; 
b.show();

現(xiàn)在,一個(gè)Base的對(duì)象被賦值為子類(lèi)Derived的對(duì)象。

結(jié)果是:

Base

Base

對(duì)于熟悉Java的人而言,這不可理解。但實(shí)際上,C++不是Java,它更像C?!癰 = d”的意思,并不是Java中的“讓一個(gè)指向Base類(lèi)的引用指向它的子類(lèi)對(duì)象”,而是“把Base類(lèi)的子類(lèi)對(duì)象中的Base子對(duì)象分割出來(lái),賦值給b”。所以,只要b的類(lèi)型始終是Base,那么b.show()調(diào)用的永遠(yuǎn)都是Base類(lèi)中的show函數(shù);換句話說(shuō),編譯器總是把Base中的那個(gè)show函數(shù)的入口地址作為b.show()的入口地址。這根本就沒(méi)用上多態(tài)。

單繼承下的重寫(xiě)多態(tài)

那我們?cè)龠@樣:

Base b; 
Derived d;
Base *p = &b;

p->show();
p = &d;
p->show();

這時(shí),結(jié)果就對(duì)了:

Base

Derived

p是一個(gè)指向基類(lèi)對(duì)象的指針,第一次它指向一個(gè)Base對(duì)象,p->show()調(diào)用了Base類(lèi)的show函數(shù);而第二次它指向了一個(gè)Derived對(duì)象,p->show()調(diào)用了Derived類(lèi)的show函數(shù)。

總結(jié):也就是說(shuō),只有是指針或者引用才是真正的多態(tài),將子對(duì)象賦給父類(lèi)對(duì)象其實(shí)類(lèi)型向上轉(zhuǎn)型

個(gè)人覺(jué)得C++容易弄混淆的地方(持續(xù)更新):

1.const和指針的修飾問(wèn)題

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(也就是類(lèi)型本身或者是 *variable對(duì)指針的解引用)就是指針指向的內(nèi)容是常量,反之就是修飾指針本身的。那我們可以總結(jié)一個(gè)識(shí)別方法就是:看const 兩邊(當(dāng)然有的只有一邊)的類(lèi)型是類(lèi)型(指針指向的內(nèi)容)就是類(lèi)型變量本身是常量(如const char * a和char const a 的const兩邊是char,a)。

當(dāng)然兩者都是常量就是:const char * const a;第一個(gè)const是類(lèi)型常量,第二個(gè)才是指針常量。同樣給出 const char &a ;const char *a;在傳遞參數(shù)時(shí)使用。

2.數(shù)組和指針的組合問(wèn)題

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++變量的初始化

對(duì)于內(nèi)置類(lèi)型局部變量不進(jìn)行初始化,但是分配地址,全局變量會(huì)進(jìn)行默認(rèn)初始化。對(duì)于類(lèi)類(lèi)型局部變量(沒(méi)有顯式初始化)會(huì)進(jìn)行默認(rèn)初始化(有默認(rèn)構(gòu)造函數(shù),否則報(bào)錯(cuò)),但其內(nèi)部的內(nèi)置數(shù)據(jù)成員不會(huì)進(jìn)行初始化(如果在默認(rèn)構(gòu)造函數(shù)沒(méi)有進(jìn)行初始化)。數(shù)組也是同樣。

參考文章

《深度探索C++對(duì)象模型(Inside The C++ Object Model )》學(xué)習(xí)筆記:https://dsqiu.iteye.com/blog/1669614

c++,為什么要引入虛擬繼承 :https://www.cnblogs.com/mylinux/p/4725833.html

RTTI、虛函數(shù)和虛基類(lèi)的實(shí)現(xiàn)方式、開(kāi)銷(xiāo)分析及使用指導(dǎo):http://baiy.cn/doc/cpp/inside_rtti.htm

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 從前我很鄙視微商, 現(xiàn)在,我自己就是一名微商。 自從淘寶解決了萬(wàn)千人民的就業(yè)問(wèn)題之后,微信的發(fā)展也逐漸催生出了一個(gè)...
    一只大瑞閱讀 2,132評(píng)論 0 0
  • Everyone may encounter all kinds of intersections, someti...
    大林dalin閱讀 571評(píng)論 0 50
  • 核心內(nèi)容部分 一、具有以下特質(zhì)有助于提高成功的心理預(yù)期1、認(rèn)同努力與結(jié)果的協(xié)變。2、相信對(duì)于結(jié)果的控制源自于自身,...
    昆明家庭教育聯(lián)盟閱讀 1,087評(píng)論 0 1
  • 今天是星期日,我和媽媽打掃衛(wèi)生,我們先掃地,后有又拖地。我自己收拾了我的學(xué)習(xí)桌,又把我們曬干的收拾衣服收拾起來(lái)。屋...
    董子璐閱讀 257評(píng)論 0 0
  • test2 test2 API列表 API名稱(chēng) 認(rèn)證方式 描述 getUserInfo APP ge...
    tommyhxh閱讀 1,551評(píng)論 0 0

友情鏈接更多精彩內(nèi)容