類別物件複製涉及拷貝建構函式。若未自訂,編譯器提供預設版本執行「成員逐一複製」(淺拷貝)。當類別管理動態資源(堆記憶體、檔案控制代碼等)時,淺拷貝會導致資源擁有權混淆、雙重釋放崩潰,必須實作深拷貝。
class Test {
int p1;
public:
Test(int t) : p1(t) {}
// 編譯器隱式產生:Test(const Test& rhs) : p1(rhs.p1) {}
};
對純量成員(int、double 等)足夠,但指標成員僅複製位址,雙方指向同一塊記憶體。
class Internet {
char name[20], address[30];
char* cname; // 動態配置
public:
Internet(const char* n, const char* a) {
strcpy(name, n); strcpy(address, a);
cname = new char[strlen(n)+1];
strcpy(cname, n);
}
// 深拷貝建構函式
Internet(const Internet& src) {
strcpy(name, src.name);
strcpy(address, src.address);
cname = new char[strlen(src.name)+1]; // 重新配置
strcpy(cname, src.name);
}
~Internet() { delete[] cname; }
};
關鍵:在拷貝建構函式中為指標成員 new 新記憶體並複製內容,析構函式釋放自有記憶體,確保各物件擁有獨立資源。
若拷貝建構函式僅執行 cname = src.cname,兩物件共享同一記憶體區塊。第一個物件銷毀時 delete[] 釋放記憶體,第二個物件銷毀時對已釋放記憶體再次 delete[],導致未定義行為(通常崩潰)。
函數以值傳回類別物件會產生暫時物件,觸發拷貝建構函式。編譯器優化(RVO/NRVO)可能省略拷貝,但語意上仍視為拷貝發生。
Internet a = Internet(...);:編譯器優化為直接建構 a,不呼叫拷貝建構函式。Internet& a = Internet(...);:參考綁定暫時物件,延長其生命週期至參考離開作用域,同樣不呼叫拷貝建構函式(但析構時機不同)。