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

C++ 對象拷貝深度解析:淺拷貝陷阱、深拷貝實現與三/五法則

     編輯:C++ 入門

引言:默認拷貝的隱患

C++ 編譯器默認生成的拷貝構造函數與拷貝賦值運算符執行逐成員淺拷貝。對於包含裸指針管理動態資源的類,這會導致雙重釋放、內存洩漏、懸垂指針等災難性後果。本文通過經典 Table 類案例,徹底講透淺拷貝禍根、深拷貝正確實現與現代 C++ 資源管理法則。

災難現場:淺拷貝導致的雙重釋放


class Table {
    Name* p;       // 裸指針管理動態數組
    size_t sz;
public:
    Table(size_t s = 15) : p(new Name[s]), sz(s) {}
    ~Table() { delete[] p; } // 釋放資源
    // 無自定義拷貝構造/賦值 → 編譯器生成淺拷貝
};

void h() {
    Table t1;        // t1.p 指向內存塊 A
    Table t2 = t1;   // 淺拷貝:t2.p = t1.p (同指向 A)
    Table t3;
    t3 = t2;         // 淺拷貝:t3.p = t2.p (同指向 A),t3 原內存洩漏
} // 作用域結束,析構順序:t3, t2, t1
  // t3.~Table() delete[] p (釋放 A)
  // t2.~Table() delete[] p (二次釋放 A → 未定義行為/崩潰)
  // t1.~Table() delete[] p (三次釋放 A)

深拷貝正確實現:規則三/五

當類管理資源(內存、文件句柄、Socket、鎖)時,必須自定義:

  1. 拷貝構造函數 T(const T&)
  2. 拷貝賦值運算符 T& operator=(const T&)
  3. 析構函數 ~T()
  4. (C++11) 移動構造函數 T(T&&)
  5. (C++11) 移動賦值運算符 T& operator=(T&&)

這是著名的「三法則」/「五法則」。

深拷貝代碼實現 (C++98 風格)


class Table {
    Name* p;
    size_t sz;
public:
    Table(size_t s = 15) : p(new Name[s]), sz(s) {}
    
    // 1. 拷貝構造:分配新內存,逐元素拷貝
    Table(const Table& t) : p(new Name[t.sz]), sz(t.sz) {
        for (size_t i = 0; i < sz; ++i) p[i] = t.p[i];
    }
    
    // 2. 拷貝賦值:檢查自賦值,釋放舊資源,分配新,拷貝數據
    Table& operator=(const Table& t) {
        if (this != &t) {             // 自賦值檢查
            delete[] p;               // 釋放舊資源
            p = new Name[t.sz];       // 分配新資源
            sz = t.sz;
            for (size_t i = 0; i < sz; ++i) p[i] = t.p[i];
        }
        return *this;                 // 返回引用支持鏈式賦值
    }
    
    ~Table() { delete[] p; }
};

異常安全改進:Copy-and-Swap 惯用法


#include <algorithm> // std::swap

class Table {
    Name* p;
    size_t sz;
    void swap(Table& other) noexcept { // 交換內部指針
        std::swap(p, other.p);
        std::swap(sz, other.sz);
    }
public:
    Table(size_t s = 15) : p(new Name[s]), sz(s) {}
    
    Table(const Table& t) : p(new Name[t.sz]), sz(t.sz) {
        std::copy(t.p, t.p + sz, p);
    }
    
    Table& operator=(Table t) { // 參數按值傳遞 (觸發拷貝構造)
        swap(t);                // 與臨時對象交換資源
        return *this;           // 臨時對象析構,釋放舊資源
    } // 強異常安全:若拷貝構造拋異常,原對象不變
    
    ~Table() { delete[] p; }
};

優勢:代碼復用拷貝構造,自動處理自賦值,提供強異常安全保證。

現代 C++ 終極方案:智能指針 / 容器 (Rule of Zero)

不要手寫資源管理類! 使用標準庫組件,讓編譯器自動生成的默認操作正確無誤。


#include <memory>
#include <vector>
#include <string>

// 方案 1: unique_ptr 獨佔所有權
class TableUnique {
    std::unique_ptr p; // 數組專用刪除器
    size_t sz;
public:
    TableUnique(size_t s = 15) : p(std::make_unique(s)), sz(s) {}
    // 編譯器生成的拷貝構造/賦值 = delete (unique_ptr 不可拷貝)
    // 移動構造/賦值 = 默認正確 (轉移所有權)
    // 析構 = 默認正確 (自動 delete[])
};

// 方案 2: vector 管理數組 (推薦)
class TableVec {
    std::vector elems; // 內存自動管理,支持拷貝/移動
public:
    TableVec(size_t s = 15) : elems(s) {}
    // 所有特殊成員函數 = 默認正確 (Rule of Zero)
};

// 方案 3: shared_ptr 共享所有權
class TableShared {
    std::shared_ptr p; // 需自定義刪除器或用 shared_ptr(new Name[s], deleter)
    size_t sz;
public:
    TableShared(size_t s = 15) : p(new Name[s], std::default_delete()), sz(s) {}
    // 默認拷貝/移動/析構均正確 (引用計數)
};

總結:資源管理決策樹

  1. 能用 std::vector/std::string/std::map 等容器 → 直接用,**零手寫管理代碼**。
  2. 需獨佔動態資源 → std::unique_ptr (配合自定義刪除器)。
  3. 需共享動態資源 → std::shared_ptr。
  4. 必須手寫管理 (極罕見) → 遵循五法則,優先用 Copy-and-Swap 實現賦值,提供強異常安全。
  5. 禁用裸指針管理資源 — 這是現代 C++ 核心準則。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved