日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

编译器角度看C++复制构造函数

發布時間:2024/4/17 c/c++ 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 编译器角度看C++复制构造函数 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

[C++對象模型]復制構造函數的建構操作

關于復制構造函數的簡單介紹,可以看我以前寫過的一篇文章C++復制控制之復制構造函數該文章中介紹了復制構造函數的定義、調用時機、也對編譯器合成的復制構造函數行為做了簡單說明。本文因需要會涉及到上文的一些知識點,但還是推薦先閱讀上文。

本文主要從編譯器角度對復制構造函數進行分析,糾正以前對復制構造函數的一些錯誤認識。

淺拷貝(deep copy)與深拷貝(shallow copy)

我們首先來看復制構造函數涉及的兩個概念:淺拷貝與深拷貝。假設有兩個對象:A與B,它們是同類型的,下面分析B=A時淺拷貝與深拷貝行為。

淺拷貝:

淺拷貝簡單地把B復制為A的引用或指針,可以認為B復制了A的地址,復制的結果是B與A擁有相同的地址,它們將指向相同的內存區域的相同的數據。在這種情況下,如果對象A被銷毀,那么對對象B的某些操作將是非法的。

深拷貝:

深拷貝時使用一個對象的內容來創建同一個類的另一個實例,B復制了A的所有成員,并在內存中不同于A的區域為B分配了存儲空間,也即是說B擁有自己的資源。在這種方式下,如果A被銷毀時,B依舊有效,因為A與B并沒有共享存儲空間,重載復制操作符時要采用這種深拷貝方式。

當你明確知道你中程序中使用的是淺拷貝并且明白它帶來的后果時你才去使用淺拷貝。而當你有大量的指針要處理時,對指針做淺拷貝是一個糟糕的做法。如果我們類的數據成員都是內置類型而沒有指針,那么簡單的淺拷貝是可以接受的,反之如果類中有需要深層復制的內容,則我們的復制構造函數必須以深拷貝的方式進行對象的復制。

Memberwise copy 與 Bitwise copy

Memberwise copy:

逐個成員:我們把merberwise copy當成deep copy來理解就行了,這種復制會根據每個成員的類型來進行復制,對于指針類型會復制指針所指的值(重新分配存儲區域)。

Bitwise copy:

Bitwise copy 字面上的意思是逐位拷貝。舉個例子,對于兩個同類型的對象A與B,對象A在內存中占據存儲區為0x0-0x9,執行B=A時,使用Bitwise copy拷貝語義,那么將會拷貝0x0到0x9的數據到B的內存地址,也就是說Bitwise是字節到字節的拷貝。這樣子理解起來,實際上Bitwise copy = shallow copy。

類的Bitwise copy 語意

《Effective C++》中說到:

如果你自己沒聲明,編譯器就會為它聲明一個copy構造函數、一個copy assignment操作符和一個析構函數。

實際上在《深度探索C++對象模型》中對編譯器的行為并不是這樣描述的。對于默認構造函數與復制構造函數,都需要類滿足一定的條件時編譯器才會幫你合成。那么需要滿足些什么條件呢?這條件就是:類不展現bitwise copy 語意的時候。

類展現Bitwise copy語意

當我們的類中只含有內置類型或復合類型時,類展現了Bitwise copy 語意。這種情況下并不需要合成一個默認復制構造函數,也即編譯器不會幫我們合成復制構造函數。如:


//以下聲明展現了bitwise copy 語意
class Word
{
public:
Word(chartemp){ str = temp; };
//...
int cnt;
char str;
};

這時候如果我們有兩個Word對象的賦值操作如下:

int main()
{
char * temp = "hello";
Word A(temp);
Word B(A);
cout << B.str;
system("pause");
return 0;
}

運行程序,你會神奇地發現程序居然通得過編譯,而且B也得到了正確的賦值,就好像類中有了一個復制構造函數一樣。不是說編譯器在Bitwise copy語意下不會進行復制構造函數的合成嗎?

說實話這問題我也很疑惑,查看了許多資料,反復看了《深度探索C++對象模型》后,我最終這樣認為:展現了Bitwise copy語意的類編譯器不會為它寫一個函數實體進行成員的復制。展現Bitwise copy語意的類,類的數據成員按照Memberwise Initialization(注意不同于Memberwise copy)進行初始化,具體是這樣的:當類對象以同類型的另一個對象進行初始化時,把每一個內建的或派生的date member(例如一個指針或一數目組)的值,從一個對象拷貝到另一個對象,不過它并不會拷貝其中的member class object,而是以遞歸的方式施行以上的拷貝。實施這些步驟并不在函數實體內。

類不展現Bitwise copy語意

當類不展現出Bitwise copy語意且類設計者沒有為類定義一個復制構造函數,這時編譯器就會為合成一個復制構造函數實體。那么在什么情況下一個類才會不展現出Bitwise copy 語意呢?

  • 當類內含有一個member object 而后者的類聲明中有個復制構造函數時(無論這個構造函數是設計者明確地聲明還是編譯器合成)。
  • 當類繼承于一個基類而后者有已給復制構造函數時(同樣的,無論基類的構造函數是設計者明確聲明的還是合成的)。
  • 當類聲明了一個或多個虛函數時。
  • 當類派生自一個繼承串鏈,其中有一個或多個虛基類時。
  • 前兩種情況中,編譯器必須將“類成員或基類的復制構造函數調用操作”安插到新合成的復制構造函數中去,如果類設計者已經明確聲明了一個復制構造函數,則這些調用操作代碼將插入到已有的復制構造函數中去(在函數體的最前端插入)。

    后兩種操作涉及到了虛表指針與虛基類指針的產生于初值設置。我們知道,當一個類含有虛函數時(無論這虛函數是類本身定義還是繼承而來),在編譯期間會有以下兩個程序擴張操作:

    • 為類增加一個虛表(virtual function table),虛表內含有每一個有作用的虛函數的地址。
    • 為每一個類對象增加一個虛表指針(vptr),虛表指針指向了該類的虛表。

    顯然,如果編譯器對每個新定義的類對象不能正確地設置好初值,將導致嚴重的后果。所以編譯器需要合成出一個復制構造函數來適當地初始化類對象的vptr。萬一類設計者明確定義了自己的復制構造函數,則編譯器會把設置vptr的操作插入到已有的復制構造函數中。而vptr的復制又有兩種情況:

    • 同類型對象間的vptr復制

    同類類型的對象各自的vptr總是指向了同一個位置:該類的虛表指針。這時兩個對象的vptr的復制都可以直接考”bitwise copy“來完成(除了可能會有的其他指針成員)。所以同類型對象間的vptr復制總是安全的。

    -把子類對象vptr復制給父類對象

    不用擔心把子類對象復制給父類對象時,vptr也會采用bitwise copy來復制,這點編譯器給我們做了保證:編譯器合成的默認構造函數(或者說在明確聲明的復制構造函數中安插的代碼)會明確設定父類的vptr指向父類的虛函數表,而不是采用傻瓜式直接復制子類對象vptr。

    而對于第4點涉及到虛基類的情況,可以看C++合成默認構造函數的真相中有關虛基類的描述。虛基類的存在需要特殊處理,一個類對象如果以另一個對象作為初值,而后者派生于虛基類,那么這種情況下bitwise copy語意也會失效,編譯器會對派生自虛基類的類合成一個默認構造函數,在其中安插一些操作。對于虛繼承,編譯器有承偌:派生類對象中的虛基類位置在執行期就要準備妥當,維護”位置的完整性“是編譯器的責任,而顯然的,Bitwise copy 語意會破壞這個位置(這種傻瓜式的復制好像只適用內置類型的復制以及同類型對象間vptr的復制),所以編譯器必須在它自己合成出來的復制構造函數中做出仲裁。同樣的,如果類設計者明確聲明了復制構造函數,則這些沖裁代碼將安插在這個復制構造函數中。

    總結

    在類不滿足"Bitwise copy"語意時編譯器會采取行動,如果類設計者沒有明確定義復制構造函數,則編譯器將行動實施于合成構造函數中,否則將這些行動實施于已有的復制構造函數中。值得注意的是,編譯器除了對vptr與虛基類的處理能保證安全之外,對于內置類型或復合類型如指針的復制都是采用淺拷貝,所以,當我們的類中含有指針的時候,我們需要自己寫一個復制構造函數來對對象的指針進行深拷貝,而vptr與虛基類的問題,就交給編譯器吧!

    轉載于:https://www.cnblogs.com/QG-whz/p/4716139.html

    總結

    以上是生活随笔為你收集整理的编译器角度看C++复制构造函数的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。