天天看點

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
{
    本(ref 中 域 C b)//DIP前為普通構造函數,本dip後為複制構造函數
    {
        導入 std.stdio:writeln;
        writeln(" Yo ");
    }
}

空 函數(C c){}

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