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、鎖)時,必須自定義:
T(const T&)T& operator=(const T&)~T()T(T&&)T& operator=(T&&)這是著名的「三法則」/「五法則」。
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; }
};
#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; }
};
優勢:代碼復用拷貝構造,自動處理自賦值,提供強異常安全保證。
不要手寫資源管理類! 使用標準庫組件,讓編譯器自動生成的默認操作正確無誤。
#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) {}
// 默認拷貝/移動/析構均正確 (引用計數)
};
std::vector/std::string/std::map 等容器 → 直接用,**零手寫管理代碼**。std::unique_ptr (配合自定義刪除器)。std::shared_ptr。