<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          深度探索C++對(duì)象模型筆記

          共 9253字,需瀏覽 19分鐘

           ·

          2021-11-20 02:59

          一、關(guān)于對(duì)象

          • C 語言是程序性的,語言本身并沒有支持?jǐn)?shù)據(jù)和函數(shù)之間的關(guān)聯(lián)性

          • C++ 中可能采取抽象數(shù)據(jù)類型,或者是多層次的類結(jié)構(gòu)完成

          • C++ 的封裝并沒有增加多少成本,每一個(gè)成員函數(shù)雖然在class中聲明,但是卻不出現(xiàn)在每個(gè)對(duì)象中

            • 每一個(gè)非內(nèi)聯(lián)的成員函數(shù)只會(huì)誕生一個(gè)函數(shù)實(shí)例
            • 每個(gè)內(nèi)聯(lián)函數(shù)會(huì)在其每一個(gè)使用者身上產(chǎn)生一個(gè)函數(shù)實(shí)例
          • C++ 在布局以及存儲(chǔ)時(shí)間上主要的額外負(fù)擔(dān)是由virtual引起的

            • 虛函數(shù)機(jī)制用以支持一個(gè)有效率的“執(zhí)行期綁定
            • 虛基類用來實(shí)現(xiàn)“多次出現(xiàn)在繼承關(guān)系中的基類,有一個(gè)單一而被共享的實(shí)例
          • 還有一些多重繼承下的額外負(fù)擔(dān),發(fā)生在一個(gè)派生類和其第二或后繼之基類的轉(zhuǎn)換之間

          1.1 C++對(duì)象模式

          • C++對(duì)象模型有以下幾點(diǎn)
            • 每個(gè)類中存放一個(gè)指針稱為vptr,指向虛函數(shù)表
            • 表中每個(gè)都指向一個(gè)虛函數(shù)
            • 非靜態(tài)數(shù)據(jù)成員放在類對(duì)象內(nèi)
            • 靜態(tài)數(shù)據(jù)成員放在類對(duì)象
            • 靜態(tài)和非靜態(tài)成員函數(shù)也放在類對(duì)象
            • 虛函數(shù)則不同
          C++對(duì)象模型

          1.2 關(guān)鍵詞所帶來的差異

          • int ( *pq ) ( ); //聲明

          • 當(dāng)語言無法區(qū)分那是一個(gè)聲明還是一個(gè)表達(dá)式時(shí),我們需要一個(gè)超越語言范圍的規(guī)則,而該規(guī)則會(huì)將上述式子判斷為一個(gè)“聲明“

          • struct和class可以相互替換,他們只是默認(rèn)的權(quán)限不一樣

          • 如果一個(gè)程序員需要擁有C聲明的那種struct布局,可以抽出來單獨(dú)成為struct聲明,并且和C++部分組合起來

          1.3 對(duì)象的差異

          • C++支持三種程序范式:程序模型、抽象數(shù)據(jù)類型模型、面向?qū)ο竽P?/strong>

            • 面向?qū)ο竽P驮诶^承體系中 ,有時(shí)候編譯期間無法確定指針或引用所指類型
          • C++支持的多態(tài)類型:

            1. 經(jīng)由一組隱式的轉(zhuǎn)化操作:如派生類指針轉(zhuǎn)化為指向父類的指針
            2. 經(jīng)由虛函數(shù)機(jī)制
            3. 經(jīng)由dynamic_cast 和 typeid運(yùn)算符
          • 一個(gè)class所占的大小包括:

            • 其非靜態(tài)成員所占的大小
            • 由于內(nèi)存對(duì)齊填補(bǔ)上的大小
            • 加上支持虛函數(shù)而產(chǎn)生的大小
          • 指針的類型,只能代表其讓編譯器如何解釋其所指向的地址內(nèi)容,和它本身類型無關(guān),所以轉(zhuǎn)換其實(shí)是一種編譯器指令,不改變所指向的地址,只影響怎么解釋它給出的地址

          • 當(dāng)一個(gè)基類對(duì)象被初始化為一個(gè)子類對(duì)象時(shí),派生類就會(huì)被切割用來塞入較小的基類內(nèi)存中,派生類不會(huì)留下任何東西,多態(tài)也不會(huì)再呈現(xiàn)。

          二、構(gòu)造函數(shù)語意學(xué)

          2.1 默認(rèn)構(gòu)造函數(shù)的構(gòu)造操作

          • 以下四種情況下,會(huì)合成有用的構(gòu)造函數(shù):

            • 類聲明(或繼承)一個(gè)虛函數(shù)
            • 類派生自一個(gè)繼承串鏈,其中有一個(gè)或更多的虛基類
            • 帶有默認(rèn)構(gòu)造函數(shù)的成員函數(shù)對(duì)象,不過這個(gè)合成操作只有在構(gòu)造函數(shù)真正需要被調(diào)用時(shí)才發(fā)生,但只是調(diào)用其成員的默認(rèn)構(gòu)造函數(shù),其他則不會(huì)初始化
            • 如果一個(gè)派生類的父類帶有默認(rèn)構(gòu)造函數(shù),那么子類如果沒有定義構(gòu)造函數(shù),則會(huì)合成默認(rèn)構(gòu)造函數(shù),如果有的話但是沒有調(diào)用父類的,則編譯器會(huì)插入一些代碼調(diào)用父類的默認(rèn)構(gòu)造函數(shù)
            • 帶有一個(gè)虛函數(shù)的類
            • 帶有一個(gè)虛基類的類
          • C++新手常見的兩個(gè)誤解

            • 任何class如果沒有定義默認(rèn)構(gòu)造函數(shù),就會(huì)被合成出來一個(gè)
            • 編譯器合成出來的默認(rèn)構(gòu)造函數(shù)會(huì)顯式設(shè)定類中的每一個(gè)數(shù)據(jù)成員的額 默認(rèn)值

          2.2 拷貝構(gòu)造函數(shù)的構(gòu)造操作

          • 有三種情況會(huì)調(diào)用拷貝構(gòu)造函數(shù):

            • 對(duì)一個(gè)對(duì)象做顯式的初始化操作
            • 當(dāng)對(duì)象被當(dāng)作參數(shù)交給某個(gè)函數(shù)
            • 當(dāng)函數(shù)傳回一個(gè)類對(duì)象時(shí)
          • 如果類沒有聲明一個(gè)拷貝函數(shù),就會(huì)有隱式的聲明和隱式的定義出現(xiàn),同默認(rèn)構(gòu)造函數(shù)一樣在使用時(shí)才合成出來

          • 什么情況下一個(gè)類不展現(xiàn)“淺拷貝語意”:

            • 編譯器會(huì)合成一個(gè)拷貝構(gòu)造函數(shù),安插一些代碼用來設(shè)定虛基類指針和偏移的初值,對(duì)每個(gè)成員執(zhí)行必要的深拷貝初始化操作,以及執(zhí)行其他的內(nèi)存相關(guān)工作
            • 編譯器會(huì)顯式的設(shè)定新類的虛函數(shù)表,而不是直接拷貝過來指向同一個(gè)
            • 這兩個(gè)編譯器都會(huì)合成拷貝構(gòu)造函數(shù)并且安插進(jìn)那個(gè)成員和基類的拷貝構(gòu)造函數(shù)
            • 當(dāng)類內(nèi)含有一個(gè)成員類而后者的類聲明中有一個(gè)拷貝構(gòu)造函數(shù)(例如內(nèi)含有string成員變量)
            • 當(dāng)類繼承自一個(gè)基類而基類中存在拷貝構(gòu)造函數(shù)
            • 當(dāng)類聲明了一個(gè)或多個(gè)虛函數(shù)
            • 當(dāng)類派生自一個(gè)繼承串鏈,其中有一個(gè)或多個(gè)虛基類

          2.3 程序轉(zhuǎn)化語意學(xué)

          • 在將一個(gè)類作為另一個(gè)類的初值情況下,語言允許編譯器有大量的自由發(fā)揮的空間,用來提升效率,但是缺點(diǎn)是不能安全的規(guī)劃拷貝構(gòu)造函數(shù)的副作用,必須視其執(zhí)行而定

          • 拷貝構(gòu)造的應(yīng)用,編譯器會(huì)多多少的進(jìn)行部分轉(zhuǎn)換,尤其是當(dāng)一個(gè)函數(shù)以值傳遞的方式傳回一個(gè)對(duì)象,而該對(duì)象有一個(gè)合成的構(gòu)造函數(shù),此外編譯器也會(huì)對(duì)拷貝構(gòu)造的調(diào)用進(jìn)行調(diào)優(yōu),以額外的第一參數(shù)取代NRV(Named Return Value)

          2.4 成員們的初始化隊(duì)伍

          • 四種情況下你需要使用成員初始化列表
            • 當(dāng)初始化一個(gè)引用成員變量
            • 當(dāng)初始化一個(gè)const 成員變量
            • 當(dāng)調(diào)用一個(gè)基類的構(gòu)造函數(shù),而它擁有一組參數(shù)
            • 當(dāng)調(diào)用一個(gè)類成員變量的構(gòu)造函數(shù),而它擁有一組參數(shù)
          class?Word{
          ?String?_name;
          ?int?_cnt;
          public:
          ?Word(){
          ??_name?=?0;
          ??_cnt?=?0;
          ?}
          /*使用成員列表初始化可以解決????
          ?????Word()?:?_name(0),_cnt(0){

          ?}
          */
          ?
          }

          上式不會(huì)報(bào)錯(cuò),但是會(huì)有效率問題,因?yàn)?strong>這樣會(huì)先產(chǎn)生一個(gè)臨時(shí)的string對(duì)象,然后將它初始化,之后以一個(gè)賦值運(yùn)算符將臨時(shí)對(duì)象指定給_name,再摧毀臨時(shí)的對(duì)象

          • 成員初始化列表中的初始化順序是按照類中的成員變量聲明的順序,與成員初始化列表的排列順序無關(guān)

          3、Data語意學(xué)

          class?X{};
          class?Y?:?public?virtual?X?{};
          class?Z?:?public?virtual?X?{};
          class?A?:?public?Y,public?Z?{};

          sizeof(X)?//1
          sizeof(Y)?//4
          sizeof(Z)?//4
          sizeof(A)?//8
          • X為1是因?yàn)榫幾g器的處理,在其中插入了1個(gè)char,為了讓其對(duì)象能在內(nèi)存中有自己獨(dú)立的地址

          • Y,Z是因?yàn)樘摶惐淼闹羔?/strong>

          • A 中含有Y和Z所以是8

          • 每一個(gè)類對(duì)象大小的影響因素:

            • 非靜態(tài)成員變量的大小
            • virtual特性
            • 內(nèi)存對(duì)齊

          3.1 數(shù)據(jù)成員的綁定

          • 如果類的內(nèi)部有typedef,請(qǐng)把它放在類的起始處,因?yàn)榉乐瓜瓤吹降氖侨值暮瓦@個(gè)typedef相同的沖突,編譯器會(huì)選擇全局的,因?yàn)橄瓤吹饺值?/strong>

          3.2 數(shù)據(jù)成員的布局

          • 非靜態(tài)成員變量的在內(nèi)存中的順序和其聲明順序是一致的
          • 但是不一定是連續(xù)的,因?yàn)橹虚g可能有內(nèi)存對(duì)齊的填補(bǔ)物
          • virtual機(jī)制的指針?biāo)诺奈恢煤途幾g器有關(guān)

          3.3 成員變量的存取

          • 靜態(tài)變量都被放在一個(gè)全局區(qū),與類的大小無關(guān),正如對(duì)其取地址得到的是與類無關(guān)的數(shù)據(jù)類型,如果兩個(gè)類有相同的靜態(tài)成員變量,編譯器會(huì)暗自為其名稱編碼,使兩個(gè)名稱都不同
          • 非靜態(tài)成員變量則是直接放在對(duì)象內(nèi),經(jīng)由對(duì)象的地址和在類中的偏移地址取得,但是在繼承體系下,情況就會(huì)不一樣,因?yàn)榫幾g器無法確定此時(shí)的指針指的具體是父類對(duì)象還是子類對(duì)象

          3.4 繼承下的數(shù)據(jù)成員

          • 在下面給定的兩個(gè)類中依次討論不同情況:


          原本的數(shù)據(jù)模型
          • 在單一繼承沒有虛函數(shù)的情況下布局圖

          單一繼承且無虛函數(shù)
          • 這種情況下常見錯(cuò)誤
            • 可能會(huì)重復(fù)設(shè)計(jì)一些操作相同的函數(shù),我們可以把某些函數(shù)寫成inline,這樣就可以在子類中調(diào)用父類的某些函數(shù)來實(shí)現(xiàn)簡(jiǎn)化
            • 把數(shù)據(jù)放在同一個(gè)類中和繼承起來的內(nèi)存布局可能不同,因?yàn)槊總€(gè)類需要內(nèi)存對(duì)齊

            • 疊在一起的內(nèi)存布局

          分層繼承的布局

          可見內(nèi)存大了100%

          • 容易出現(xiàn)的不易發(fā)現(xiàn)的問題:

          繼承下易犯錯(cuò)誤
          • 當(dāng)加上多態(tài)之后,對(duì)空間上增加的額外負(fù)擔(dān)包括:

            • 析構(gòu)函數(shù)的調(diào)用順序是反向的,從子類到父類
            • 導(dǎo)入一個(gè)虛函數(shù)表,表中的個(gè)數(shù)是聲明的虛函數(shù)的個(gè)數(shù)加上一個(gè)或兩個(gè)slots(用來支持運(yùn)行類型識(shí)別)

            • 在每個(gè)對(duì)象中加入vptr,提供執(zhí)行期的鏈接,使每一個(gè)類能找到相應(yīng)的虛函數(shù)表

            • 加強(qiáng)構(gòu)造函數(shù),使它能夠?yàn)関ptr設(shè)定初值,讓它指向?qū)?yīng)的虛函數(shù)表,這可能意味著在派生類和每一個(gè)基類的構(gòu)造函數(shù)中,重新設(shè)定vptr的值

            • 加強(qiáng)析構(gòu)函數(shù),使它能夠消抹“指向類的相關(guān)虛函數(shù)表”的vptr,vptr很可能以及在子類析構(gòu)函數(shù)中被設(shè)定為子類的虛表地址。

            • 以下是三種情況不同的繼承下會(huì)有不同的布局

            • Vptr放在尾端
            • vptr放在前端

          含虛函數(shù)的數(shù)據(jù)分布
          • 多重繼承

          • **單一繼承特點(diǎn):**派生類和父類對(duì)象都是從相同的地址開始,區(qū)別只是派生類比較大能容納自己的非靜態(tài)成員變量

          • 多重繼承下會(huì)比較復(fù)雜

          多重繼承關(guān)系
          • 一個(gè)派生對(duì)象,把它的地址指定給最左邊的基類,和單一繼承一樣,因?yàn)槠鹗嫉刂肥且粯拥?,但是后面的需要更改,因?yàn)樾枰由锨懊婊惖拇笮?,才能得到后面基類的地?/strong>

          多重繼承數(shù)據(jù)分布
          • 虛繼承

          • STL標(biāo)準(zhǔn)庫中使用的虛繼承:

          • 虛繼承關(guān)系圖
          • 虛繼承關(guān)系:


            虛繼承例子

          • 虛繼數(shù)據(jù)在內(nèi)存中的分布

          • 虛繼承數(shù)據(jù)模型
          • 虛繼承數(shù)據(jù)模型2

          3.5 對(duì)象成員的效率

          • 程序員如果關(guān)心程序效率,應(yīng)該實(shí)際測(cè)試,不要光憑推論、常識(shí)判斷或假設(shè)。
          • 優(yōu)化操作并不一定總是能夠有效運(yùn)行,我不止一次以優(yōu)化方式來 編譯一個(gè)已通過編譯的正常程序,卻以失敗收?qǐng)?/section>

          3.6 指向數(shù)據(jù)成員的指針

          • vptr通常放在起始處或尾端,與編譯器有關(guān),C++標(biāo)準(zhǔn)允許放在類中的任何位置

          • 取某個(gè)類成員變量的地址,通常取到得的是在類的首地址的偏移位置

            • 例如 & Point3d::z; 將得到在類的偏移位置,最低限度是類的成員大小總和,而這個(gè)偏移量通常都被加上了1
          • 如果用一個(gè)真正綁定類對(duì)象(也就是使用 . 操作符訪問成員變量)去取地址,得到的將會(huì)是內(nèi)存中真正的地址

          • 在多重繼承下,若要蔣第二個(gè)積累的指針和一個(gè)與派生類綁定的成員結(jié)合起來,那么將會(huì)因?yàn)樾枰尤肫屏慷兊孟喈?dāng)復(fù)雜

          四、Function 語意學(xué)

          • C++支持三種類型的成員函數(shù):static 、non-static 、virtual
            • 不能直接存取non-static數(shù)據(jù)
            • 不能被聲明為const
            • static函數(shù)限制

          4.1 成員函數(shù)的各種調(diào)用方式

          • 非靜態(tài)成員函數(shù):C++會(huì)保證至少和一般的普通的函數(shù)有相同的效率,經(jīng)過三個(gè)步驟的轉(zhuǎn)換

            • 改寫函數(shù),安插一個(gè)額外的參數(shù)到該函數(shù)中,用來提供一個(gè)存取管道------即this指針
            • 對(duì)每一個(gè)非靜態(tài)成員的存取操作改成使用this指針來調(diào)用
            • 將成員函數(shù)改寫成一個(gè)外部函數(shù),并且名稱改為獨(dú)一無二的
          • 虛函數(shù)成員函數(shù):也會(huì)經(jīng)過類似的轉(zhuǎn)化

            • vptr是編譯器產(chǎn)生的指針,指向虛函數(shù)表,其名稱也會(huì)被改為獨(dú)一無二
            • 1 是該函數(shù)在虛函數(shù)表中的索引
            • ptr 則是this指針
            • 例如:ptr->normalize()會(huì)被轉(zhuǎn)化為
            • ( * ptr->vptr[1] )( ptr )
          • 靜態(tài)成員函數(shù):它沒有this指針,因此會(huì)有以下限制

            • 它不能直接存取類中的非成員變量
            • 它不能夠被聲明為const、volatile 和 virtual
            • 它不需要經(jīng)過類的對(duì)象才能被調(diào)用----雖然很多事情況是這樣調(diào)用的

          4.2 詳解虛成員函數(shù)

          • 虛函數(shù)一般實(shí)現(xiàn)模型:每一個(gè)類都有一個(gè)虛函數(shù)表,內(nèi)含該類中有作用的虛函數(shù)地址,然后每個(gè)對(duì)象有一個(gè)虛函數(shù)指針,指向虛表位置

          • 多態(tài)含義:以一個(gè)基類的指針(或引用),尋址出一個(gè)子類對(duì)象

          • 什么是積極多態(tài)?

          • 當(dāng)被指出的對(duì)象真正使用時(shí),多態(tài)就變成積極的了

          • 單一繼承虛函數(shù)布局圖

          單一繼承虛函數(shù)圖
          • 一個(gè)類派生自Point會(huì)發(fā)生什么事?三種可能的情況

            • 它可以繼承基類所聲明的虛函數(shù)的函數(shù)實(shí)例該函數(shù)實(shí)例的地址會(huì)被拷貝進(jìn)子類的虛表的相對(duì)應(yīng)的slot之中
            • 它可以使用自己的虛函數(shù)實(shí)例----它自己的函數(shù)實(shí)例地址必須放在對(duì)應(yīng)的slot之中
            • 它可以加入一個(gè)新的虛函數(shù),這時(shí)候虛函數(shù)表的尺寸會(huì)增加一個(gè)slot,而新加入的函數(shù)實(shí)例地址也會(huì)被放進(jìn)該slot之中
          • 多重繼承下的虛函數(shù)

          • 多繼承虛函數(shù)圖
          • 虛擬繼承下的虛函數(shù)

          • 虛繼承下的虛函數(shù)圖
          • 4.4指向成員函數(shù)的指針

          • 對(duì)于普通的成員函數(shù),編譯器會(huì)將其轉(zhuǎn)化為一個(gè)函數(shù)指針,然后使用成員函數(shù)的地址去初始化

            • 例如 ?double (Point :: *pmf ) ( );
            • 轉(zhuǎn)化為 ?double ( Point :: coord )( ) = &Point :: x;
            • 這樣調(diào)用 ?(coord) (& origin)或 (coord)(ptr)
          • 對(duì)一個(gè)虛函數(shù)取地址,在vc編譯器下,要么得到vacll thunk地址(虛函數(shù)時(shí)候),要么得到的是函數(shù)地址(普通函數(shù))

          4.5 內(nèi)聯(lián)函數(shù)

          • inline只是向編譯器提出一個(gè)請(qǐng)求,是否真的優(yōu)化取決于編譯器自己的判定

          • 對(duì)于形式參數(shù),會(huì)采用:

            • 常量表達(dá)式替換
            • 常量替換
            • 引入臨時(shí)變量來避免多次求值操作
          • 對(duì)于局部變量,會(huì)采用:

            • 使用臨時(shí)變量

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

          • 應(yīng)注意的一些問題:
            • 構(gòu)造函數(shù)不要寫為純虛函數(shù),因?yàn)楫?dāng)抽象類中有數(shù)據(jù)的時(shí)候,將無法初始化
            • 把所有函數(shù)設(shè)計(jì)成虛函數(shù),再由編譯器去除虛函數(shù)是錯(cuò)誤的,不應(yīng)該成為虛函數(shù)的函數(shù)不要設(shè)計(jì)成虛函數(shù)
            • 當(dāng)你無法抉擇一個(gè)函數(shù)是否需要為const時(shí),尤其是抽象類,最好不設(shè)置成const

          5.1 "無繼承" 情況下的對(duì)象構(gòu)造

          • 對(duì)于沒有初始化的全局變量,C語言中會(huì)將其放在一個(gè)未初始化全局區(qū),而C++會(huì)將所有全局對(duì)象初始化
          • 對(duì)于不需要構(gòu)造函數(shù)、析構(gòu)函數(shù)、賦值操作的類或結(jié)構(gòu)體,C++會(huì)將其打上POD標(biāo)簽,賦值時(shí)按照c那樣的位搬運(yùn)
          • 對(duì)于含有虛函數(shù)的類,編譯器會(huì)在構(gòu)造函數(shù)的開始放入一些初始化虛表和虛指針的操作
          • 面對(duì)函數(shù)以值方式返回,編譯器會(huì)將其優(yōu)化為加入一個(gè)參數(shù)的引用方式,避免多次構(gòu)造函數(shù)

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

          • 構(gòu)造函數(shù)會(huì)含有大量的隱藏嘛,因?yàn)榫幾g器會(huì)進(jìn)行擴(kuò)充

            • 如果類被列于成員初始化列表中,那么如果有任何顯式指定的參數(shù),都應(yīng)該傳遞過去。若沒有列于list中,而類中有一個(gè)默認(rèn)構(gòu)造,亦應(yīng)該調(diào)用
            • 此外,類中的每一個(gè)虛基類的偏移位置必須在執(zhí)行期可被存取
            • 如果類對(duì)象是最底層的類,其構(gòu)造函數(shù)可能被調(diào)用,某些用以支持這一行為的機(jī)制必須被放進(jìn)來
            • 如果基類被列于成員初始化列表中,那么任何顯式指定的參數(shù)應(yīng)該傳遞過去
            • 如果基類沒有被列于基類初始化列表中,而它有默認(rèn)的構(gòu)造函數(shù),那么就調(diào)用
            • 如果基類是多重繼承下的第二個(gè)或后繼的基類,那么this指針必須有所調(diào)整
            • 記錄在成員初始化列表中的成員數(shù)據(jù)初始化會(huì)被放進(jìn)構(gòu)造函數(shù)的本體,并以成員在類中聲明的順序?yàn)轫樞?/section>
            • 如果有一個(gè)成員并沒有出現(xiàn)在成員初始化列表中,但是它由一個(gè)默認(rèn)構(gòu)造函數(shù),那么也會(huì)被調(diào)用
            • 在那之前,如果類對(duì)象由虛表指針,它必須被設(shè)定初值,指向適當(dāng)?shù)奶摫?/strong>
            • 在那之前,所有上一層的基類構(gòu)造函數(shù)必須被調(diào)用,以基類聲明的順序(不是成員初始化列表出現(xiàn)的順序)
            • 在那之前,所有虛基類構(gòu)造函數(shù)必須被調(diào)用,從左到右,從最深到最淺
          • 不要忘記在賦值函數(shù)中,檢查自我賦值的情況

          5.3 對(duì)象復(fù)制語意學(xué)

          • 當(dāng)一個(gè)類復(fù)制給另一個(gè)類時(shí),能采用的有三種方式:

            • 如果有需要,會(huì)自動(dòng)生成一個(gè)淺拷貝,至于什么時(shí)候需要深拷貝(見第二章講)
            • 什么都不做,會(huì)實(shí)施默認(rèn)行為
            • 提供一個(gè)拷貝復(fù)制運(yùn)算符
            • 顯式地拒絕把一個(gè)類拷貝給另一個(gè)
          • 虛基類會(huì)使其復(fù)制操作調(diào)用一次以上,因此我們應(yīng)該避免在徐杰類中聲明數(shù)據(jù)成員

          5.5 析構(gòu)語意學(xué)

          • 什么時(shí)候會(huì)合成析構(gòu)函數(shù)?

            • 在類內(nèi)含的成員函數(shù)有析構(gòu)函數(shù)
            • 基類含有析構(gòu)函數(shù)
          • 析構(gòu)的正確順序

            • 析構(gòu)函數(shù)的本體首先被執(zhí)行,vptr會(huì)在程序員的代碼執(zhí)行前被重設(shè)。
            • 如果類擁有成員類對(duì)象,而后者擁有析構(gòu)函數(shù),那么他們會(huì)以其聲明順序的相反順序被調(diào)用
            • 如果類內(nèi)涵一個(gè)vptr,現(xiàn)在被重新設(shè)定,指向適當(dāng)?shù)姆e累的虛表
            • 如果有任何直接的非虛基類擁有析構(gòu)函數(shù),它們會(huì)以其聲明順序的相反順序被調(diào)用
            • 如果有任何虛基類擁有析構(gòu)函數(shù),而且目前討論的這個(gè)類是最尾端的類,那么它們會(huì)以其原來的構(gòu)造順序的相反順序被調(diào)用

          六、執(zhí)行期語意學(xué)

          • C++難以從程序源碼看出表達(dá)式的復(fù)雜過程,因?yàn)槟悴⒉恢谰幾g器會(huì)在其中添加多少代碼

          • 編譯器對(duì)不同的對(duì)象會(huì)做不同的操作:

            • 如果對(duì)象沒有定義構(gòu)造函數(shù)和析構(gòu)函數(shù),那么編譯器只需要分配需要存儲(chǔ)10個(gè)連續(xù)空間即可
            • 如果有構(gòu)造函數(shù),且如果有名字,則會(huì)分為是否含有虛基類調(diào)用不同的函數(shù)來構(gòu)造,如果沒有名字,例如沒有knots,則會(huì)使用new來分配在堆中
            • 當(dāng)聲明結(jié)束時(shí)會(huì)有類似構(gòu)造的析構(gòu)過程
            • 我們無法在程序中取出一個(gè)構(gòu)造函數(shù)的地址
            • 會(huì)增加臨時(shí)的對(duì)象用來判斷其是否被構(gòu)造,用來保證在第一次進(jìn)入含有該靜態(tài)對(duì)象的起始處調(diào)用一次構(gòu)造函數(shù),并且在離開文件的時(shí)候利用臨時(shí)對(duì)象判斷是否已經(jīng)被構(gòu)造來決定是否析構(gòu)
            • 如果異常處理被支持,那么那些對(duì)象將不能被放置到try區(qū)段之內(nèi)
            • 增加了程序的復(fù)雜度
            • 對(duì)于全局對(duì)象:編譯器會(huì)在添加__main函數(shù)和_exit函數(shù)(和C庫中的不同),并且在這兩個(gè)函數(shù)中對(duì)所有全局對(duì)象進(jìn)行靜態(tài)初始化和析構(gòu)

              全局對(duì)象編譯器操作
            • 使用被靜態(tài)初始化的對(duì)象,有一些缺點(diǎn)

            • 因此,不建議用那些需要靜態(tài)初始化的全局對(duì)象

            • 對(duì)于局部靜態(tài)對(duì)象

            • 對(duì)于對(duì)象數(shù)組:(例如Point knots[10]

          6.2 new 和 delete 運(yùn)算符

          • 對(duì)于普通類型變量

          • 例如 int *pi = new int(5)

            • 調(diào)用函數(shù)庫中的new運(yùn)算符if(int *pi = _new (sizeof(int) ))
            • 再配置初值 *pi = 5
          • 對(duì)于delete來說 delete pi;

            • 則先進(jìn)行保護(hù) if( pi != 0)
            • 再調(diào)用delete ? _delete(pi)
          • 對(duì)于成員對(duì)象

            • 對(duì)于Point3d* origin = new Point3d

            • 實(shí)際調(diào)用operator new,其代碼如下

            • extern?void*?operator?new?(size_t?size){
              ????if(size?==?0)
              ????????size?=?1;
              ????void?*?last_alloc;
              ????while(!(last_alloc?=?malloc(size))){
              ????????if(_new_handler)
              ????????????(?*_new_handler)();
              ????????else
              ????????????return?0;
              ????}
              ????return?last_alloc;
              }
            • 語言要求每一次對(duì)new的調(diào)用都必須傳回一個(gè)獨(dú)一無二的指針,為了解決這個(gè)問題,傳回一個(gè)指向默認(rèn)為1Byte的內(nèi)存區(qū)塊,允許程序員自己定義_new_handler函數(shù),并且循環(huán)調(diào)用

            • 至于delete也相同

              extern?void?operator?delete?(void?*ptr){
              ?if(ptr)
              ??free(?(char*)ptr)
              }
            • 對(duì)于對(duì)象數(shù)組,會(huì)在分配的內(nèi)存上方放上cookies,來存儲(chǔ)數(shù)組個(gè)數(shù),方便delete調(diào)用來析構(gòu)

            • 程序員最好避免以一個(gè)基類指向一個(gè)子類所組成的數(shù)組---如果子類對(duì)象比其基類大的話

            • 解決方式:

            • for(int?ix?=?0;?ix??Point3d?*p?=?&((Point3d*)ptr)[ix];
              ?delete?p;
              }
            • 程序員必須迭代走過整個(gè)數(shù)組,把delete運(yùn)算符實(shí)施與每一個(gè)元素身上。以此方式,調(diào)用操作將是virtual。因此,Point3d和Point的析構(gòu)函數(shù)都會(huì)實(shí)施與每一個(gè)對(duì)象上

          • Placement Operator new的語意

            • 有一個(gè)預(yù)先定義好的重載的new運(yùn)算符,稱為placement operator new。它需要第二個(gè)參數(shù),類型為void*

            • 形如 Point2w *ptw = new (arena) Point2w,其中arena指向內(nèi)存中的一個(gè)區(qū)塊,用以放置新產(chǎn)生出來的Point2w 對(duì)象

            • void*?operator?new(size_t?,?void*?p){
              ?return?p;
              }
            • 如果我們?cè)谝延袑?duì)象的基礎(chǔ)上調(diào)用placement new的話,原來的析構(gòu)函數(shù)并不會(huì)被調(diào)用,而是直接刪除原來的指針,但是不能使用delete 原來的指針

            • 正確的方法應(yīng)該是 :

              //錯(cuò)誤:
              delete?p2w;
              p2w?=?new(arwna)?Point2w;
              //正確:
              p2w->Point2w;
              p2w?=?new(arena)?Point2w;
          • 6.3 臨時(shí)性對(duì)象

          • 臨時(shí)對(duì)象在類的表達(dá)式并賦值,函數(shù)以值方式傳參等都會(huì)產(chǎn)生臨時(shí)對(duì)象-----而臨時(shí)對(duì)象會(huì)構(gòu)造和析構(gòu),所以會(huì)拖慢程序的效率,我們應(yīng)該盡量避免

          七、站在對(duì)象模型的頂端

          • 三個(gè)著名的C++語言擴(kuò)充性質(zhì):模板、異常(EH)、RTTI(runtime type identification)

          • 7.1 Template

          • 模板實(shí)例化時(shí)間線

            • 當(dāng)編譯器看到模板類的聲明時(shí),什么都不會(huì)做,不會(huì)進(jìn)行實(shí)例化
            • 模板類中明確類型的參數(shù),通過模板類的某個(gè)實(shí)例化版本才能存取操作
            • 即使是靜態(tài)類型的變量,也需要與具體的實(shí)例版本關(guān)聯(lián),不同版本有不同的一份
            • 如果聲明一個(gè)模板類的指針,那么不會(huì)進(jìn)行實(shí)例化,但是如果是引用,那么會(huì)進(jìn)行實(shí)例化
            • 對(duì)于模板類中的成員函數(shù),只有在函數(shù)使用的時(shí)候才會(huì)進(jìn)行實(shí)例化
          • 模板名稱決議的方法-----即如果非成員函數(shù)在類中調(diào)用,那么會(huì)調(diào)用名稱相同的哪個(gè)版本:

            • 會(huì)根據(jù)該函數(shù)是否與模板有關(guān)來判斷,如果是已知類型,那么會(huì)在定義的范圍內(nèi)直接查找,如果以來模板的具體類型,那么會(huì)在實(shí)例化的范圍查找
          • 7.2 異常處理

          • C++異常處理三個(gè)主要的語匯組件:

            • 一個(gè)throw子語。它在程序某處發(fā)出一個(gè)exception,exception可以說內(nèi)建類型也可以是自定義類型
            • 一個(gè)或多個(gè)catch子句。每一個(gè)catch子句都是一個(gè)exceotion hander,它用來表示說,這個(gè)子句準(zhǔn)備處理某種類型exception,并且在封閉的大括號(hào)區(qū)段中提供實(shí)際的處理程序
            • 一個(gè)try區(qū)段。它被圍繞一系列的敘述句,這些敘述句可能會(huì)引發(fā)catch子句起作用
          • 當(dāng)一個(gè)異常被拋出去,控制權(quán)會(huì)從函數(shù)調(diào)用中被釋放出來,并尋找一個(gè)吻合的catch子句。如果都沒有吻合者,那么默認(rèn)的處理例程 terminate()會(huì)被調(diào)用,當(dāng)控制權(quán)被放棄后,堆棧中的每一個(gè)函數(shù)調(diào)用也就被推離。這個(gè)程序被稱為 unwingding the stack 。在每一個(gè)函數(shù)被推離堆棧之前,函數(shù)的局部類的析構(gòu)會(huì)被調(diào)用

          • 因此一個(gè)解決辦法就是將類封裝在一個(gè)類中,這樣變成局部類,如果拋出異常也會(huì)被自動(dòng)析構(gòu)

          • 當(dāng)一個(gè)異常拋出,編譯系統(tǒng)必須

            • 摧毀所有活躍局部類
            • 從堆棧中將目前的函數(shù)(unwind)釋放掉
            • 進(jìn)行到程序堆棧的下一個(gè)函數(shù)中去,然后重復(fù)上述步驟2—5
            1. 檢查發(fā)生throw操作的函數(shù)
            2. 決定throw操作是否發(fā)生在try區(qū)段
            3. 若是,編譯系統(tǒng)必須把異常類型拿來和每一個(gè)catch子句進(jìn)行比較
            4. 如果比較后吻合,流程控制應(yīng)該交到catch子句中
            5. 如果throw的發(fā)生并不在try區(qū)段中,或沒有一個(gè)catch子句吻合,那么系統(tǒng)必須:
          • 7.3 執(zhí)行期類型識(shí)別

          • 當(dāng)兩個(gè)類有繼承關(guān)系的時(shí)候,我們有轉(zhuǎn)換需求時(shí),可以進(jìn)行向下轉(zhuǎn)型,但是很多時(shí)候是不安全的

          • C++的RTTI (執(zhí)行期類型識(shí)別)提供了一個(gè)安全的向下轉(zhuǎn)型設(shè)備,但是只對(duì)多態(tài)(繼承和動(dòng)態(tài)綁定)的類型有效,其用來支持RTTI的策略就是,在C++的虛函數(shù)表的第一個(gè)slot處,放一個(gè)指針,指向保存該類的一些信息----即type_info類(在編譯器文件中可以找到其定義)

          • dynamic_cast運(yùn)算符可以在執(zhí)行期決定真正的類型

            • 如果引用真正轉(zhuǎn)換到適當(dāng)?shù)淖宇?,則可以繼續(xù)
            • 如果不能轉(zhuǎn)換的化,會(huì)拋出 bad_cast 異常
            • 通常使用 try 和 catch 來進(jìn)行判斷是否成功
            • 如果轉(zhuǎn)型成功,則會(huì)返回一個(gè)轉(zhuǎn)換后的指針
            • 如果是不安全的,會(huì)傳回0
            • 放在程序中通過 if 來判斷是否成功,采取不同措施
            • 對(duì)于指針來說:
            • 對(duì)于引用來說:
          • Typeid運(yùn)算符

            • 可以傳入一個(gè)引用,typeid運(yùn)算符會(huì)傳回一個(gè)const reference,類型為type_info。其內(nèi)部已經(jīng)重載了 == 運(yùn)算符,可以直接判斷兩個(gè)是否相等,回傳一個(gè)bool值
            • 例如 ?if( typeid( rt ) == typeid( fct ) )
            • RTTI雖然只適用于多態(tài)類,但是事實(shí)上type_info object也適用于內(nèi)建類,以及非多態(tài)的使用者自定類型,只不過內(nèi)建類型 得到的type_info類是靜態(tài)取得,而不是執(zhí)行期取得
          瀏覽 49
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    亚洲成人福利视频 | 在线a片免费观看 | NP玩烂了公用爽灌满视频播放 | 操逼视频动漫 | 在线观看国产免费视频 |