複制構造函數
概述
本文檔提出了複制構造函數,它是後複制函數的替代方法.複制構造函數消除了對後複制的需求,并修複了其缺陷和固有限制.還讨論了演進和向後相容性.
理由和動機
本節重點介紹了
後複制
存在問題,并說明了為什麼
複制構造函數
比
後複制
好.
本(本)概述
無法有意義地
重載或限定
後複制函數.但,編譯器不會拒絕限定器應用,如下所示:
構 A{本(本)常 {}}
構 B{本(本)不變{}}
構 C{本(本)共享{}}
存在限定詞的情況下,未定義後複制的語義,實驗表明該行為是打更新檔式的:
- 常 不能修改目标中的任何字段的後複制
- 不變 永遠不調用後複制(導緻編譯錯誤)
-
共享 後複制位不能保證原子性
定義和實作
将破壞在目前語義下經過測試并認為正确的代碼.有意義語義
考慮常/不變後遺症
上面是一大堆問題的理由,不看了.
引入複制構造函數
如上所述,不嚴格限制,後複制很難檢查類型,且同步成本過高.
該DIP提出具有以下優點的
複制構造
函數:
- 該特征在C++語言上效果很好[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
,
隐式
生成複制構造函數:
- S 未顯式聲明任何複制構造函數;
- 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);
}