首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C++ 拷貝構造函數深度解析:從淺拷貝到深拷貝的完整指南

     編輯:關於C語言

什麼是拷貝構造函數?

在 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++ 標準規定,以下三種情況會觸發拷貝構造函數:

1. 對象以值傳遞方式傳入函數參數

void g_Fun(CExample C) { // 形參為值傳遞
    cout << "test" << endl;
}
int main() {
    CExample test(1);
    g_Fun(test); // 這裡會調用拷貝構造函數創建臨時對象 C
    return 0;
}

過程:創建臨時對象 → 調用拷貝構造函數複製實參 → 函數結束時析構臨時對象。

2. 對象以值傳遞方式從函數返回

CExample g_Fun() {
    CExample temp(0);
    return temp; // 返回時調用拷貝構造函數創建臨時返回值對象
}

注意:現代編譯器會優化(RVO/NRVO),可能省略此次拷貝。

3. 對象通過另一個對象進行初始化


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() {}
};

任何嘗試拷貝的代碼都會導致編譯錯誤。

拷貝構造函數的進階細節

1. 拷貝構造函數能訪問 private 成員嗎?

能。 拷貝構造函數本質是構造函數,屬於類的成員函數,享有訪問所有 private/protected 成員的權限。

2. 哪些函數簽名屬於拷貝構造函數?

對於類 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); // 是

3. 一個類可以有多個拷貝構造函數嗎?

可以。 例如同時提供 const 和非 const 版本:


class X {
public:
    X(const X&); // const 版本
    X(X&);       // 非 const 版本
};

若僅提供 X(X&),則無法用 const X 對象初始化新對象。

總結

  • 拷貝構造函數用於同類型對象初始化,參數必須是引用。
  • 三大調用場景:值傳遞參數、值返回、對象初始化。
  • 淺拷貝僅複製成員值,適合無動態資源的類;深拷貝需手動實現,為指針成員分配新記憶體。
  • 通過 private 拷貝構造函數可禁止拷貝語義。
  • 理解 const 引用參數、多重拷貝構造函數重載等細節,能寫出更健壯的 C++ 代碼。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved