<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ì)象的底層原理都在這兒了,還敢說(shuō)學(xué)不會(huì)?

          共 9371字,需瀏覽 19分鐘

           ·

          2021-12-01 16:27


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

          C 語(yǔ)言是程序性的,語(yǔ)言本身并沒(méi)有支持?jǐn)?shù)據(jù)和函數(shù)之間的關(guān)聯(lián)性C++ 中可能采取抽象數(shù)據(jù)類型,或者是多層次的類結(jié)構(gòu)完成 C++ 的封裝并沒(méi)有增加多少成本,每一個(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í)行期綁定” 虛基類用來(lái)實(shí)現(xiàn)“多次出現(xiàn)在繼承關(guān)系中的基類,有一個(gè)單一而被共享的實(shí)例

          還有一些多重繼承下的額外負(fù)擔(dān),發(fā)生在一個(gè)派生類和其第二或后繼之基類的轉(zhuǎn)換之間

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

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

          C++對(duì)象模型

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

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

          struct和class可以相互替換,他們只是默認(rèn)的權(quán)限不一樣如果一個(gè)程序員需要擁有C聲明的那種struct布局,可以抽出來(lái)單獨(dú)成為struct聲明,并且和C++部分組合起來(lái)

          1.3 對(duì)象的差異

          C++支持三種程序范式:程序模型、抽象數(shù)據(jù)類型模型、面向?qū)ο竽P?/strong>面向?qū)ο竽P驮诶^承體系中 ,有時(shí)候編譯期間無(wú)法確定指針或引用所指類型

          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)容,和它本身類型無(wú)關(guān),所以轉(zhuǎn)換其實(shí)是一種編譯器指令,不改變所指向的地址,只影響怎么解釋它給出的地址

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

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

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

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

          C++新手常見(jiàn)的兩個(gè)誤解任何class如果沒(méi)有定義默認(rèn)構(gòu)造函數(shù),就會(huì)被合成出來(lái)一個(gè)編譯器合成出來(lái)的默認(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í)

          如果類沒(méi)有聲明一個(gè)拷貝函數(shù),就會(huì)有隱式的聲明和隱式的定義出現(xiàn),同默認(rèn)構(gòu)造函數(shù)一樣在使用時(shí)才合成出來(lái) 什么情況下一個(gè)類不展現(xiàn)“淺拷貝語(yǔ)意”:當(dāng)類內(nèi)含有一個(gè)成員類而后者的類聲明中有一個(gè)拷貝構(gòu)造函數(shù)(例如內(nèi)含有string成員變量) 當(dāng)類繼承自一個(gè)基類而基類中存在拷貝構(gòu)造函數(shù)這兩個(gè)編譯器都會(huì)合成拷貝構(gòu)造函數(shù)并且安插進(jìn)那個(gè)成員和基類的拷貝構(gòu)造函數(shù)當(dāng)類聲明了一個(gè)或多個(gè)虛函數(shù)編譯器會(huì)顯式的設(shè)定新類的虛函數(shù)表,而不是直接拷貝過(guò)來(lái)指向同一個(gè)當(dāng)類派生自一個(gè)繼承串鏈,其中有一個(gè)或多個(gè)虛基類編譯器會(huì)合成一個(gè)拷貝構(gòu)造函數(shù),安插一些代碼用來(lái)設(shè)定虛基類指針和偏移的初值,對(duì)每個(gè)成員執(zhí)行必要的深拷貝初始化操作,以及執(zhí)行其他的內(nèi)存相關(guān)工作

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

          在將一個(gè)類作為另一個(gè)類的初值情況下,語(yǔ)言允許編譯器有大量的自由發(fā)揮的空間,用來(lái)提升效率,但是缺點(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ì)有效率問(wèn)題,因?yàn)?strong style="color: rgb(37, 132, 181);">這樣會(huì)先產(chǎn)生一個(gè)臨時(shí)的string對(duì)象,然后將它初始化,之后以一個(gè)賦值運(yùn)算符將臨時(shí)對(duì)象指定給_name,再摧毀臨時(shí)的對(duì)象

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

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

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

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

          原本的數(shù)據(jù)模型

          在單一繼承沒(méi)有虛函數(shù)的情況下布局圖

          單一繼承且無(wú)虛函數(shù)

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

          分層繼承的布局

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

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

          繼承下易犯錯(cuò)誤

          當(dāng)加上多態(tài)之后,對(duì)空間上增加的額外負(fù)擔(dān)包括:

          導(dǎo)入一個(gè)虛函數(shù)表,表中的個(gè)數(shù)是聲明的虛函數(shù)的個(gè)數(shù)加上一個(gè)或兩個(gè)slots(用來(lái)支持運(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è)定為子類的虛表地址。析構(gòu)函數(shù)的調(diào)用順序是反向的,從子類到父類

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

          vptr放在前端

          單一繼承有虛函數(shù)

          多重繼承

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

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

          多重繼承關(guān)系

          一個(gè)派生對(duì)象,把它的地址指定給最左邊的基類,和單一繼承一樣,因?yàn)槠鹗嫉刂肥且粯拥模呛竺娴男枰?,因?yàn)樾枰由锨懊婊惖拇笮?,才能得到后面基類的地?/strong>

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

          虛繼承

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

          虛繼承例子

          虛繼承關(guān)系:

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

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

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

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

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

          vptr通常放在起始處或尾端,與編譯器有關(guān),C++標(biāo)準(zhǔn)允許放在類中的任何位置 取某個(gè)類成員變量的地址,通常取到得的是在類的首地址的偏移位置例如?& Point3d::z;?將得到在類的偏移位置,最低限度是類的成員大小總和,而這個(gè)偏移量通常都被加上了1如果用一個(gè)真正綁定類對(duì)象(也就是使用 . 操作符訪問(wèn)成員變量)去取地址,得到的將會(huì)是內(nèi)存中真正的地址

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

          Part4四、Function 語(yǔ)意學(xué)

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

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

          非靜態(tài)成員函數(shù):C++會(huì)保證至少和一般的普通的函數(shù)有相同的效率,經(jīng)過(guò)三個(gè)步驟的轉(zhuǎn)換改寫函數(shù),安插一個(gè)額外的參數(shù)到該函數(shù)中,用來(lái)提供一個(gè)存取管道------即this指針對(duì)每一個(gè)非靜態(tài)成員的存取操作改成使用this指針來(lái)調(diào)用將成員函數(shù)改寫成一個(gè)外部函數(shù),并且名稱改為獨(dú)一無(wú)二的

          虛函數(shù)成員函數(shù):也會(huì)經(jīng)過(guò)類似的轉(zhuǎn)化例如:ptr->normalize()會(huì)被轉(zhuǎn)化為( * ptr->vptr[1] )( ptr )vptr是編譯器產(chǎn)生的指針,指向虛函數(shù)表,其名稱也會(huì)被改為獨(dú)一無(wú)二1 是該函數(shù)在虛函數(shù)表中的索引ptr 則是this指針

          靜態(tài)成員函數(shù):它沒(méi)有this指針,因此會(huì)有以下限制它不能直接存取類中的非成員變量它不能夠被聲明為const、volatile 和 virtual它不需要經(jīng)過(guò)類的對(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í)變量來(lái)避免多次求值操作

          對(duì)于局部變量,會(huì)采用:使用臨時(shí)變量

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

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

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

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

          不要忘記在賦值函數(shù)中,檢查自我賦值的情況

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

          當(dāng)一個(gè)類復(fù)制給另一個(gè)類時(shí),能采用的有三種方式:什么都不做,會(huì)實(shí)施默認(rèn)行為如果有需要,會(huì)自動(dòng)生成一個(gè)淺拷貝,至于什么時(shí)候需要深拷貝(見(jiàn)第二章講)提供一個(gè)拷貝復(fù)制運(yùn)算符顯式地拒絕把一個(gè)類拷貝給另一個(gè)

          虛基類會(huì)使其復(fù)制操作調(diào)用一次以上,因此我們應(yīng)該避免在虛基類中聲明數(shù)據(jù)成員

          5.5 析構(gòu)語(yǔ)意學(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ì)以其原來(lái)的構(gòu)造順序的相反順序被調(diào)用

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

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

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

          對(duì)于全局對(duì)象:編譯器會(huì)在添加__main函數(shù)和_exit函數(shù)(和C庫(kù)中的不同),并且在這兩個(gè)函數(shù)中對(duì)所有全局對(duì)象進(jìn)行靜態(tài)初始化和析構(gòu)

          全局對(duì)象編譯器操作

          使用被靜態(tài)初始化的對(duì)象,有一些缺點(diǎn)

          如果異常處理被支持,那么那些對(duì)象將不能被放置到try區(qū)段之內(nèi)增加了程序的復(fù)雜度

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

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

          會(huì)增加臨時(shí)的對(duì)象用來(lái)判斷其是否被構(gòu)造,用來(lái)保證在第一次進(jìn)入含有該靜態(tài)對(duì)象的起始處調(diào)用一次構(gòu)造函數(shù),并且在離開文件的時(shí)候利用臨時(shí)對(duì)象判斷是否已經(jīng)被構(gòu)造來(lái)決定是否析構(gòu)

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

          如果對(duì)象沒(méi)有定義構(gòu)造函數(shù)和析構(gòu)函數(shù),那么編譯器只需要分配需要存儲(chǔ)10個(gè)連續(xù)空間即可 如果有構(gòu)造函數(shù),且如果有名字,則會(huì)分為是否含有虛基類調(diào)用不同的函數(shù)來(lái)構(gòu)造,如果沒(méi)有名字,例如沒(méi)有knots,則會(huì)使用new來(lái)分配在堆中 當(dāng)聲明結(jié)束時(shí)會(huì)有類似構(gòu)造的析構(gòu)過(guò)程 我們無(wú)法在程序中取出一個(gè)構(gòu)造函數(shù)的地址

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

          對(duì)于普通類型變量:例如int *pi = new int(5) 調(diào)用函數(shù)庫(kù)中的new運(yùn)算符if(int *pi = _new (sizeof(int) )) 再配置初值 *pi = 5 對(duì)于delete來(lái)說(shuō) 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;
          ????}

          語(yǔ)言要求每一次對(duì)new的調(diào)用都必須傳回一個(gè)獨(dú)一無(wú)二的指針,為了解決這個(gè)問(wèn)題,傳回一個(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,來(lái)存儲(chǔ)數(shù)組個(gè)數(shù),方便delete調(diào)用來(lái)析構(gòu)

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

          解決方式:

          ?for(int?ix?=?0;?ix???Point3d?*p?=?&((Point3d*)ptr)[ix];
          ??delete?p;
          ?}

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

          Placement Operator new的語(yǔ)意

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

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

          ?void*?operator?new(size_t?,?void*?p){
          ??return?p;
          ?}

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

          正確的方法應(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)該盡量避免

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

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

          7.1 Template

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

          當(dāng)編譯器看到模板類的聲明時(shí),什么都不會(huì)做,不會(huì)進(jìn)行實(shí)例化模板類中明確類型的參數(shù),通過(guò)模板類的某個(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)來(lái)判斷,如果是已知類型,那么會(huì)在定義的范圍內(nèi)直接查找,如果依賴模板的具體類型,那么會(huì)在實(shí)例化的范圍查找

          7.2 異常處理

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

          一個(gè)throw子語(yǔ)。它在程序某處發(fā)出一個(gè)exception,exception可以說(shuō)內(nèi)建類型也可以是自定義類型一個(gè)或多個(gè)catch子句。每一個(gè)catch子句都是一個(gè)exceotion hander,它用來(lái)表示說(shuō),這個(gè)子句準(zhǔn)備處理某種類型exception,并且在封閉的大括號(hào)區(qū)段中提供實(shí)際的處理程序一個(gè)try區(qū)段。它被圍繞一系列的敘述句,這些敘述句可能會(huì)引發(fā)catch子句起作用

          當(dāng)一個(gè)異常被拋出去,控制權(quán)會(huì)從函數(shù)調(diào)用中被釋放出來(lái),并尋找一個(gè)吻合的catch子句。如果都沒(méi)有吻合者,那么默認(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)必須

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

          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)綁定)的類型有效,其用來(lái)支持RTTI的策略就是,在C++的虛函數(shù)表的第一個(gè)slot處,放一個(gè)指針,指向保存該類的一些信息----即type_info類(在編譯器文件中可以找到其定義)dynamic_cast運(yùn)算符可以在執(zhí)行期決定真正的類型對(duì)于指針來(lái)說(shuō):如果轉(zhuǎn)型成功,則會(huì)返回一個(gè)轉(zhuǎn)換后的指針如果是不安全的,會(huì)傳回0放在程序中通過(guò) if 來(lái)判斷是否成功,采取不同措施對(duì)于引用來(lái)說(shuō):如果引用真正轉(zhuǎn)換到適當(dāng)?shù)淖宇悾瑒t可以繼續(xù)如果不能轉(zhuǎn)換的話,會(huì)拋出 bad_cast 異常通常使用 try 和 catch 來(lái)進(jìn)行判斷是否成功

          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)的使用者自定類型,只不過(guò)內(nèi)建類型 得到的type_info類是靜態(tài)取得,而不是執(zhí)行期取得






          END
          瀏覽 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>
                    www.午夜| 蜜臀av无码精品一区二区三区 | 思思热免费视频在线观看 | 国产无遮挡又黄又爽 | 国产黄色一级大片 |