在 C++ 中,基本類型(如 int、double)的賦值非常直觀:
int a = 100;
int b = a; // 直接複製數值
然而,類對象擁有複雜的內部結構(成員變量、動態分配的記憶體等),簡單的位元複製往往不夠。此時,拷貝構造函數 便登場了——它是一種特殊的構造函數,用於使用同類型的現有對象來初始化新對象。
拷貝構造函數的參數必須是 本類型的引用(通常為 const 引用),形式如下:
ClassName(const ClassName& other);
以下示例展示了編譯器自動生成的默認拷貝構造函數的行為:
#include <iostream>
using namespace std;
class CExample {
private:
int a;
public:
CExample(int b) { a = b; }
void Show() { cout << a << endl; }
};
int main() {
CExample A(100);
CExample B = A; // 調用拷貝構造函數(等價於 CExample B(A);)
B.Show(); // 輸出 100
return 0;
}
若我們顯式定義拷貝構造函數,可以控制複製邏輯:
class CExample {
private:
int a;
public:
CExample(int b) { a = b; }
// 自定義拷貝構造函數
CExample(const CExample& C) { a = C.a; }
void Show() { cout << a << endl; }
};
C++ 標準規定,以下三種情況會觸發拷貝構造函數:
void g_Fun(CExample C) { // 形參為值傳遞
cout << "test" << endl;
}
int main() {
CExample test(1);
g_Fun(test); // 這裡會調用拷貝構造函數創建臨時對象 C
return 0;
}
過程:創建臨時對象 → 調用拷貝構造函數複製實參 → 函數結束時析構臨時對象。
CExample g_Fun() {
CExample temp(0);
return temp; // 返回時調用拷貝構造函數創建臨時返回值對象
}
注意:現代編譯器會優化(RVO/NRVO),可能省略此次拷貝。
CExample A(100);
CExample B = A; // 拷貝初始化
CExample C(A); // 直接初始化,同樣調用拷貝構造函數
若類中未定義拷貝構造函數,編譯器會生成一個執行逐成員複製的默認版本。這對於不包含指針或動態資源的類通常足夠,但會導致兩個問題:
static int count,拷貝時不會增加,導致對象計數錯誤。delete,導致程序崩潰。
class Rect {
public:
Rect() { p = new int(100); }
~Rect() { if(p) delete p; }
private:
int *p; // 動態分配的指針
};
int main() {
Rect rect1;
Rect rect2(rect1); // 淺拷貝:rect1.p == rect2.p
return 0; // 析構時雙重 delete 同一地址 → 崩潰
}
解決方案是在拷貝構造函數中為新對象重新分配記憶體並複製內容:
class Rect {
public:
Rect() { p = new int(100); }
// 深拷貝構造函數
Rect(const Rect& r) {
width = r.width;
height = r.height;
p = new int; // 分配新記憶體
*p = *(r.p); // 複製數值
}
~Rect() { if(p) delete p; }
private:
int width, height;
int *p;
};
此時兩個對象擁有獨立的記憶體空間,互不干擾。
若不希望對象被拷貝(如單例模式、不可複製的資源句柄),可將拷貝構造函數聲明為 private 且不實現:
class NonCopyable {
private:
NonCopyable(const NonCopyable&); // 僅聲明,不定義
NonCopyable& operator=(const NonCopyable&); // 同理禁用賦值運算符
public:
NonCopyable() {}
};
任何嘗試拷貝的代碼都會導致編譯錯誤。
能。 拷貝構造函數本質是構造函數,屬於類的成員函數,享有訪問所有 private/protected 成員的權限。
對於類 X,若構造函數的第一個參數是以下類型之一,且其餘參數都有默認值,則為拷貝構造函數:
X&const X&volatile X&const volatile X&示例:
X::X(const X&); // 是
X::X(X&, int = 1); // 是(第二個參數有默認值)
X::X(X&, int a = 1, int b = 2); // 是
可以。 例如同時提供 const 和非 const 版本:
class X {
public:
X(const X&); // const 版本
X(X&); // 非 const 版本
};
若僅提供 X(X&),則無法用 const X 對象初始化新對象。
private 拷貝構造函數可禁止拷貝語義。const 引用參數、多重拷貝構造函數重載等細節,能寫出更健壯的 C++ 代碼。