c++對象的拷貝、構造、虛構

来源:https://www.cnblogs.com/chenglixue/archive/2022/10/23/16818405.html
-Advertisement-
Play Games

抽象基類 ​ 現有如下代碼: class Abstract_base { public: virtual ~Abstract_base() = 0; virtual void interface() const = 0; virtual const char* mumble() const { re ...


抽象基類

​ 現有如下代碼:

class Abstract_base
{
public:
    virtual ~Abstract_base() = 0;
    virtual void interface() const = 0;
    virtual const char* mumble() const { return _mumble; }
    
protected:
    char* _mumble;
}

​ 以上抽象基類聲明有幾個問題:

  1. 即使class被聲明為抽象基類,其依然需要explicit constructor來初始化protected data member _mumble,否則derived class無法決定_mumble初值
  2. 抽象基類的virtual destructor不要聲明為pure。因為每個derived class destructor會被編譯器擴張,以靜態方式調用每個virtual base class和上一層base class的destructor
  3. mumble()不應聲明為virtual function,因為其定義的內容和類型無關,derived class並不會改寫此函數
//合理的聲明
class Abstract_base
{
public:
    virtual ~Abstract_base();
    virtual void interface() = 0;
    const char* mumble() const { return _mumble; }
    
protected:
    Abstract_base( char* pc = 0 );
    char* _mumble;
}
  • 一般而言,class的data member應被初始化,且只在constructor中或在class的其他member function中指定初值。其他操作都會破壞封裝性質,讓class的維護和修改變得愈加困難
  • 我們可以靜態地定義和調用一個pure virtual function,但不能經由虛擬機制
  • c++保證繼承體系中每個class object的destructors都能被調用,編譯期不可壓抑這一操作,且編譯器並沒有足夠知識合成pure virtual destructor函數定義
  • 虛擬基類中,不要把所有的member functions都聲明為virtual function,再靠編譯器的優化把非必要的虛擬調用去除
  • 虛擬基類中,virtual function不使用const

對象構造

不繼承

​ 現有如下片段:

typedef struct
{
    float x, y, z;
}Point;

Point global;

Point foobar()
{
    Point local;
    Point* heap = new Point;
    *heap = local;
    delete heap;
    return local;
}
  • 對於Point這樣的聲明,在c++會被貼上Plain OI' Data標簽。編譯器並不會為其聲明default constrcutor、destructor、copy constructor、copy assignment operator

  • 對於 Point global; 這樣的定義,在c++中members並沒有被定義或調用,行為和c如出一轍。編譯器並不會調用constructor和destructor。除非在c中,global被視作臨時性定義

    臨時性定義:因為沒有顯示初始化操作,一個臨時性定義可以在程式多次發生,但編譯器最終會將這些實例鏈接摺疊起來,只留下一個實例,放在data segment中"保留給未初始化的global object使用的"空間
    
    但在c++中並不支持臨時性定義,對於此例,會阻止後續的定義
    
  • c++中所有全局對象都被以初始化過的數據對待

  • 對於 Point* heap = new Point;編譯器並不會調用default constructor,只是 Point* heap = __new( sizeof(Point) )。delete亦是如此

  • 對於*heap = local;編譯器並不會調用copy assignment operator做拷貝,但只是像c那樣做簡單的bitwise

  • return操作也是,只是簡單的bitwise,並沒有調用copy constructor

​ 現有如下片段:

class Point
{
    public:
    	Point( float x = 0.0, float y = 0.0, float z = 0.0 ) : _x(x), _y(y), _z(z) { }
    	//沒有copy constructor,copy operator,destructor
    
    private:
    	float _x, _y, _z;
}

void do()
{
    Point local1 = {1.0, 1.0, 1.0};
}
  • 對於將class中成員設定常量值,使用explicit initialization list更有效率。因為當函數的活動記錄(activation record)被放進堆棧,initialization list的常量即可放入local1記憶體中

    活動記錄過程的調用是過程的一次活動,當過程語句(及其調用)結束後,活動生命周期結束。變數的生命周期為其從被定義後有效存在的時間
    
  • 但explicit initialization list也有不足:

    • class member需要為public
    • 只能指定常量,因為其常量在編譯器即可求值
    • 編譯器並沒有自動施行它,初始化很可能失敗
  • 若調用之前的例子,放在現在編譯器也不會調用destructor,因為並沒有顯示地提供destructor

    delete heap;
    

​ 現有如下片段:

class Point
{
    public:
    	Point( float x = 0.0, float y = 0.0, float z = 0.0 ) : _x(x), _y(y), _z(z) { }
    	virtual float z();
    	//沒有copy constructor,copy operator,destructor
    
    private:
    	float _x, _y;
}
  • 導入了virtual functions會引發編譯器對class Point的膨脹:

    • 定義的constructor附加一些代碼,以便將vptr初始化。這些代碼附加在任何base class constructors調用後,user code之前
    • 合成一個copy constructor 和 一個assignment operator,且不再是有用的(trivial),但implicit desctructor仍為有用的
  • 這種情況下,編譯器在優化狀態下可能會把object的連續內容拷貝到另一個object上,且不是memberwise。因此,編譯器會儘量延遲nontrivial members的合成操作,直到遇到合適場合

  • 例如之前的例子,此時就很有可能合成copy assignment operator,以及inline expansion。對於return,又因為合成copy constructor,函數內部又需改寫;但若編譯器支持NRV,內部會改寫為constructor,此時將不用調用copy constructor

    *heap = local;
    
    return local;
    

繼承

T object;

對於以上定義,編譯器會擴充每一個constructor。一般而言擴充如下:

  • 所有virtual base class constructors必須被調用,從左到右,從最深到最淺
    • 若class被列於member initialization list,如果有任何顯示指定的參數,都應傳過去。若沒有列於list,而class有default constructor,則調用此
    • 此外,class中的每個virtual base class subobject的offset必須在執行期可被存取
    • 若 class object 是最底層的class,其constructors可能被調用
  • 所有上層的base class constructors必須被調用,以base class的聲明順序
    • 若class被列於member initialization list,如果有任何顯示指定的參數,都應傳過去。若沒有列於list,而class有default constructor,則調用此
    • 若base class是多重繼承下的第二或後繼base class,那麼this指針需調整
  • 若class object有virtual table pointers,其需指定初值
  • 記錄在member initialization list的data member初始化會被放進constructor函數本體,以members聲明順序為順序。若有一個member沒有出現在member initialization list,但其有default constructor,那麼該default constructor必須被調用

​ 實例:

class Point
{
public:
    Point( float x = 0.0, float y = 0.0 );
    Point( const Point& );
    Point& operator=( const Point& );
    
    virtual ~Point();
    virtual float z() { return 0.0; }
    
protected:
    float _x, _y;
};

class Line
{
    Point _begin, _end;
public:
    Line( float = 0.0, float = 0.0, float = 0.0, float = 0.0 );
    Line( const Point&, const Point& ) : _end(end), _begin(begin);
    
    draw();
    ...
};

//擴充
Line* Line::Line( Line* this, const Point&, const Point& )
{
    this->begin.Point::Point( begin );
    this->end.Point::Point( end );
    return this;
}

//合成隱式的Line destructor。若Line派生自Point,合成的將會是virtual
Line a;
  • 對於虛擬繼承,以上constructor擴張方式將不再支持,這是因為virtual base class的共用性,否則會導致多次virtual base class的constructor。針對虛擬繼承,每個derived class constructor擴張方式需發生改變,添加 bool __most_derived來判斷是否為派生最底層,若為最底層則調用virtual base class consturctor

vptr初始化

​ 現有如下片段:

//假設每個class都定義了virtual function size(),傳回class大小,每個constrcutor中調用size()
Point(x,y);
Point3d(x,y,z);
Vertex(x,y,z);
Vertex3d(x,y,z);
PVertex(x,y,z);

//當我們定義PVertex object,前五個constructor各自調用自己的size()
  • 做到如上機制,我們需要在執行一個constructor時,必須限制一組virtual functions候選名單。通過控制vptr的初始化和設定
  • vptr的初始化應在base class constructors調用後,在member initialization list所列member初始化操作前

​ 此時,constructor也需改變:

  • 在derived class constructor中,調用所有virtual base classes及上一層base class 的constructors
  • 然後,初始化vptr,指向相關virtual table
  • 若有member initialization list,將在constructor展開,但必須在vptr被設定後才施行
  • 最後,執行user code
//改變後的constructor擴張
PVertex* PVertex::PVertex( PVertex* this, bool __most__derived, float x, float y, float z )
{
    //調用virtual base constructor
    if( __most_derived != false ) this->Point::Point(x,y);
    
    //調用上層base class
    this->Vertex3d::Vertex3d(x,y,z);
        
    //初始化vptr
    this->__vptr_PVertex = __vtbl_PVertex;
    this->__vptr_Point__PVertex = __vtbl_Point__PVertex;
    
    //size()
    ...
        
    return this;
}

​ 當然以上方案並不完美:

Point::Point( float x, float y ) : _x(x), _y(y) { }
Point3d::Point3d( float x, float y, float z ) : Point(x,y), _z(z) { }

​ 此時,若聲明PVertex,由於對其base class constructor的最新定義,其vptr將不再需要在每個base class constructor中被設定。因此,我們需要把constructor分裂為一個完整的object實例和sunobject實例

  • 在class的constructor的member initialization list中調用該class 的virtual functions,在語意上可能不安全,因為函數本身可能得依賴未被設立初值的members

對象複製

​ 設計一個class,並以一個class object指定給另一個class object,我們有三種選擇:

  1. 什麼都不做,實施預設行為
  2. 提供一個explicit copy assignment operator
  3. 顯示拒絕把class object指定給另一個class object。也就是將copy assignment operator聲明為private,且不提供定義
  • 只有在預設的member wise copy行為不安全或不正確時,才需要設計一個copy assignment operator。且如果class有bitwise copy,隱式的assignment operator不會合成

​ class對於default copy assignment operator,在以下情況,不會表現bitwise copy:

  1. 當class 內含member object,而其class有一個copy assignment operator
  2. 當class的base class有一個copy assignment operator
  3. 當class聲明瞭任何virtual functions。一定別拷貝右邊class object的vptr地址,它很有可能是derived class object
  4. 當class繼承自virtual base class
  • copy assignment operators並不表示bitwise copy是nontrivial。只有nontrivial instances才被合成

  • 即使賦值由bitwise copy完成,並沒有調用copy assignment operator,但還是需要提供一個copy constructor,以此打開NRV優化

  • 儘可能不要允許一個virtual base class的拷貝操作。不要在任何virtual base class中聲明數據

對象效能

  • 對於單一繼承和多重繼承,若class使用bitwise copy,一般不會合成copy constructor,就不會增加效率成本

  • 對於虛擬繼承,bitwise copy不再支持,而是合成copy assignment operator和inline copy constructor,導致成本大大增加。且繼承體系複雜度增加,對象拷貝和構造的成本也會增加

對象析構

  • 若class沒定義destructor,只有在class內含member object含有destructor時,編譯器才會合成destructor
  • 若base class不含desturctor,那麼derived class也不需要desturctor

​ destructor被擴展的方式。與constructor相似,但順序相反:

  • destructor函數本體先被執行
  • 若class含有member class object,而後者含有destructors,他們會以其聲明順序的相反順序被調用
  • 若object內含vptr,現需被重新指定,指向適當的base class的virtual table
  • 若有任何直接的nonvirtual base classes含有destructor,它們會以其相反的聲明順序被調用
  • 若由任何virtual base classes含有destructor,如之前的PVertex例子,會以其原來的構造順序的相反順序調用

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言:本篇博客詳細介紹了項目管理工具 Git 的下載安裝、環境變數配置、使用以及一些常用命令,參考了網上一些博主的介紹。有些博客只介紹下載安裝,或者只介紹 Git 命令,沒有綜合到一起。通過閱讀此博文,能夠讓對 Git 的配置與使用達到比較通達的理解,如果喜歡,請點贊收藏。 博文目錄: 一、Git ...
  • 位置: 1.找到自己項目用的解釋器存儲位置 H:\pythonProject\Lib\site-packages\django\views\generic\base.py 在base.py里有一個View類 2.也可以通過from django.views import View 按住ctrl點擊V ...
  • 正則表達式02 5.4正則表達式語法02 5.4.6捕獲分組 詳見5.3.3 例子 package li.regexp; import java.util.regex.Matcher; import java.util.regex.Pattern; //演示分組 public class RegEx ...
  • Lists,提供了很多api方便操作。例如:Lists.partition(List list,int size) Lists.partition(List list,int size)將list集合進行切割然後填充到一個List集合里。官方介紹 使用場景: 比如記憶體中有大量數據,需要迴圈調用某個方 ...
  • 緩存概述 解決不同設備間速度不匹配問題。 互聯網分層架構:降低資料庫壓力,提升系統整體性能,縮短訪問時間。 高併發問題 緩存併發(擊穿):緩存過期後將嘗試從後端資料庫獲取數據 緩存穿透:不存在的 key,請求直接落庫查詢 緩存雪崩:緩存大面積失效,請求直接落庫查詢 需求說明 通過在方法上增加緩存註解 ...
  • 文章配合圖文詳細的講解瞭如何在使用VSCode或者PHPStorm進行php的斷點調試。相關的配置內容,和需要的插件,一些要註意的點都有介紹到。說簡單挺簡單的,但是可能少了某一步就是斷點不了,讓人很頭疼。主要是瀏覽器插件可能沒裝,導致不能配合刷新瀏覽器進行斷點。希望這篇文章能對你配置php斷點能有所... ...
  • 1、依賴 <!-- swagger 核心 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.8.0</version> </dependenc ...
  • 參考文檔 COM Coding Practices Audio File Format Specifications Core Audio APIs Loopback Recording #include <iostream> #include <fstream> #include <vector> ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...