d的複製構造函數

複製構造函數

概述

本文檔提出了複製構造函數,它是後複製函數的替代方法.複製構造函數消除了對後複製的需求,並修復了其缺陷和固有限制.還討論了演進和向後兼容性.

理由和動機

本節重點介紹了後複製存在問題,並說明了爲什麼複製構造函數後複製好.

本(本)概述

無法有意義地重載或限定後複製函數.但,編譯器不會拒絕限定器應用,如下所示:

構 A{本(本)常 {}}
構 B{本(本)不變{}}
構 C{本(本)共享{}}

存在限定詞的情況下,未定義後複製的語義,實驗表明該行爲是打補丁式的:

  1. 常 不能修改目標中的任何字段的後複製
  2. 不變 永遠不調用後複製(導致編譯錯誤)
  3. 共享 後複製位不能保證原子性
    定義和實現有意義語義將破壞在當前語義下經過測試並認爲正確的代碼.

考慮常/不變後遺症

上面是一大堆問題的理由,不看了.

引入複製構造函數

如上所述,不嚴格限制,後複製很難檢查類型,且同步成本過高.

該DIP提出具有以下優點的複製構造函數:

  1. 該特徵在C++語言上效果很好[3];
  2. 可按普通構造函數檢查複製構造函數類型(由於不位複製,字段初始化方式與普通構造函數相同);即按普通構造函數檢查常/不變/共享類型.
  3. 提供封裝.

缺點是用戶必須手動複製所有字段,且每次加字段到構時,必須修改複製構造函數.但,使用D的自省機制可輕鬆搞定.如,此簡單代碼可用作語言習語或庫函數:

foreach(i,ref field;src.tupleof).tupleof[i]=字段;

如上所示,可用幾行代碼輕鬆替換後複製的好處.如下詳述,複製構造函數以最小語言複雜性解決後複製的問題.

描述

本節討論有關複製構造函數語義的所有技術方面.

句法

定義內部,如果函數聲明中第一個參數與構類型的非默認引用相同,其他所有參數都具默認值時,則爲複製構造函數.這種方式聲明複製構造函數,優點是不用改解析器,語法可不變.例:

導入 std.stdio ;

構 A
{(引用 中 域 A rhs){writeln(" x "); }              //複製構造函數(引用 中 域 A rhs,int b = 7)不變 {writeln(b); }   //使用默認參數的複製構造函數
}main()
{
    A a;
    A b = a; //隱式調用複製構造函數,打印" x" 
    A c = A(b); //顯式調用構造函數
    不變 A d = a;//隱式調用複製構造函數-打印7 
    //爲什麼打印7呢?,因爲有`不變`
}

顯式調用複製構造函數(如上面的c),因爲它也是先前語言語義中存在的構造函數.

引用傳遞複製構造函數的參數,避免無限遞歸(按值傳遞需要複製構,導致無限遞歸調用複製構造).注意,如果源是右值,則不需要調用複製構造函數,因爲將按位移動(如必要)到目標中(即,優先移動構造).例:

構  A {(引用 中 域 A rhs){}}

A 創建()
{
    靜 A 原;中 原;
}main()
{
    A 值=create();
}

僅調用一個複製構造,在rhs位置的.然後,把右值放入.

可對複製構造函數參數/函數本身應用類型限定器,以允許定義跨不同可變級對象拷貝.類型限定器是可選的.

語義學

本節討論複製構造函數和其他語言特徵間的語義分析和交互關係.

複製構造函數和後複製共存

爲確保後複製到複製構造函數的平穩過渡,此DIP提出以下策略:如果一個構定義了(用戶定義或生成的)後複製,則忽略複製構造函數,優先後複製.現有的不使用後複製代碼庫可開始使用複製構造函數,而當前依賴後複製的代碼庫可使用複製構造函數開始編寫新代碼並刪除後複製.本DIP建議棄用後複製,但未規定棄用時間表.

從後複製到複製構造函數語義上相近的轉換如下:

//新代碼
構  A
{
    本(引用 中 域 進出 A rhs)進出 { ... }
}
//替換現有代碼
構 A
{
    本(本){ ... }
     ...
}

複製構造函數用法

每當將構變量複製另一個相同類型變量時,編譯器隱式插入複製構造函數調用:

顯式初化變量時:

構 A
{(引用 中 域 A rhs){}
}main()
{
    A a;
    A b = a; //稱呼複製構造函數爲賦值
    b = a;   //而不是初化 
}

傳遞參數給函數時:

構 A
{(引用 中 域 A a){}
}

空 函數(A a){}main()
{
    A a;函數(a);//調用複製構造函數
}

從函數按值返回參數且無NRVO時:

構 A
{(引用 中 域 A a)
    {
        writeln(" cp構造器 ");
    }
}

A 函數()
{
    A a;中 a;
}

A a;
A 槍()
{
    中 a;
}main()
{
    A a=函數();// NRVO未調用複製構造函數
    A b=();//無法NRVO,調用複製構造函數
}

引用傳遞複製構造函數參數,僅當源是左值時,降低初始化至複製構造函數調用.儘管可轉發臨時左值聲明至複製構造函數,但本DIP不討論綁定右值到左值.

注意,函數返回定義了複製構造函數的構實例且無法NRVO時,在返回前,在調用點調用複製構造函數.如可NRVO,則刪除複製:

構 A
{(引用 中 域 A rhs){}
}

A a;
A 函數()
{
    中 a;//降級返回tmp.複製構造器(a)
    //中 A();//右值,不調用複製構造器
}main()
{
    A b = 函數();//用函數的返回值原位構造b 
}

檢查類型

複製構造函數與構造函數的類型檢查[6][7]一樣.
顯式禁用複製構造函數重載:

構 A
{
    @禁用 本(引用 中 域 A rhs);(ref 中 域 不變 A rhs){}
}main()
{
    A b;
    A a = b;//錯誤:禁用複製構造

    不變 A ia;
    A c = ia;//好

}

爲了禁用複製構建,必須禁用所有複製構造函數重載.在上面示例中,僅禁用了從可變到可變的複製;仍可調用從不變到可變複製重載.

重載

可用(從合格的源複製的)參數的不同限定器或複製構造函數自身(複製到合格目標)來重載複製構造函數:

構 A
{(ref 中 域 A b){}//-可變源,可變目標(ref 中 域 不變 A b){}// 2-可變源,可變目標(ref 中 域 A b)不變 {}// 3-可變源,不變目的地(引用 中 域 不變 A b)不變{}//4不變源不變目標
}main()
{
    A a;不變 A ia;
    A a2 = a;      //調用1 
    A a3 = ia;     //調用2
    不變 A a4 = a;     //調用3
    不變 A a5 = ia;    //調用4 
}

使用戶能任意組合限定:常,不變,共享,常 共享.

進出用於限定變,常或不變相同的類型:

構 A
{(引用 中 域 進出 A rhs)不變 {}
}main()
{
    r1;(A)r2;
    不變(A)r3;

    //都調用相同複製構造函數,因爲`進出`行爲就像通配符//三種情況的通配符
    不變(A)a = r1;
    不變(A)b = r2;
    不變(A)c = r3;
}

部分匹配時,適用現有重載和隱式轉換規則.

複製構造函數調用與位刷

如果構未定義複製構造函數,則右邊存儲位置位刷到左邊來初化.例:

構 A
{
    int [] a;
}main()
{
    A a = A([ 7 ]);
    A b = a;                 // mempcy(&b,&a)
    不變 A c = A([ 12 ]);
    不變 A d = c;       // memcpy(&d,&c) 
    //複製內存(匯,源);
}

構定義複製構造函數時,將對該構禁用所有隱式位刷.例:

構 A
{
    int [] a;(引用 中 域 A rhs){}
}

空 函數(不變 A){}main()
{
    不變 A a;
    函數(a);//錯誤:不能用`(不變)不變`類型調用複製構造函數 
    //沒有定義`不變(不變)`複製構造函數吧.
}

別名 本交互

構定義別名 本複製構造函數可能衝突.如返回別名 本類型是定義類型的特定版本時,有歧義.例:

構 A
{
    int * a;
    不變(A)函數()
    {
        中 不變 A();
    }

    別名 函數 本;(引用 中 域 A)不變 {}
}

構 B
{
    int * a;
    不變(B)函數()
    {
        中 不變 B();
    }

    別名 函數 本;(ref 中 域 B b){}
}//將這三個屬性合併爲一個.main()
{
    A a;不變 A ia = a;//複製構造函數
    B b;不變 B ib = b;
    //錯誤:`()不變`參數類型可能
    //不會調用複製構造函數
} 

雖然複製構造函數和別名 本都適合解決賦值問題時,複製構造函數比別名 本優先級更高,因爲它更特定(複製構造函數目的就是創建副本).如果未匹配重載集中的複製構造函數,則發出錯誤,即使使用從別名 本理論上講也可生成匹配構造器.即,在複製構造上,忽略別名 本.

生成複製構造函數

如滿足以下所有條件,編譯器對構 S,隱式生成複製構造函數:

  1. S 未顯式聲明任何複製構造函數;
  2. S 至少有一個定義具有複製構造函數的直接成員,且該成員不與任何其他成員通過重疊.

如滿足上述限制,則生成以下複製構造函數:

(引用 中 域 進出(S)src)進出
{
    foreach(i,ref 進出 field;src.tupleof).tupleof[i]=字段;
}

普通舊數據

定義複製構造函數的構不是POD.

與聯的交互

如果聯 S具有定義複製構造函數的字段,則每當S按複製初化類型對象時,發出錯誤.重疊字段(匿名聯合)也這樣.

重大變更和棄用

按源對象可變引用傳遞複製構造函數的參數.即複製構造函數調用可合法修改源對象:

構 A
{
    int [] a;(引用 中 域 A b)
    {
        b.a[2]=3;
    }
}main()
{
    A a,b;
    a = b;    //修改ba[2]
}

允許非的參數,不然,太麻煩,還要爲參數,裏面再把去掉.折騰.c++不推薦.

構 C
{(ref 中 域 C b)//DIP前爲普通構造函數,本dip後爲複製構造函數
    {
        導入 std.stdio:writeln;
        writeln(" Yo ");
    }
}

空 函數(C c){}main()
{
    C c;
    fun(c);
}

具有上述定義構造函數只用作複製構造函數.

發佈了366 篇原創文章 · 獲贊 25 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章