複製構造函數
概述
本文檔提出了複製構造函數,它是後複製函數的替代方法.複製構造函數消除了對後複製的需求,並修復了其缺陷和固有限制.還討論了演進和向後兼容性.
理由和動機
本節重點介紹了後複製
存在問題,並說明了爲什麼複製構造函數
比後複製
好.
本(本)概述
無法有意義地重載或限定
後複製函數.但,編譯器不會拒絕限定器應用,如下所示:
構 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++不推薦.
構 C
{
本(ref 中 域 C b)//DIP前爲普通構造函數,本dip後爲複製構造函數
{
導入 std.stdio:writeln;
writeln(" Yo ");
}
}
空 函數(C c){}
空 main()
{
C c;
fun(c);
}
具有上述定義構造函數只用作複製構造函數
.