| 法則 | 版本 | 內容 | 適用場景 |
| 三法則 | C++98 | 若需自定義 析構函數、複製構造函數、賦值運算符 任一,通常三者都需自定義 | 手動管理資源的類 |
| 五法則 | C++11 | 三法則 + 移動構造函數、移動賦值運算符 | 高性能、資源轉移語義 |
| 零法則 | C++11+ | **優先使用 RAII 容器/智能指針** (vector, string, unique_ptr, shared_ptr),**不聲明** 任何特殊成員函數,編譯器默認生成正確 | **現代 C++ 首選**,90%+ 場景 |
代碼演示:從淺拷貝到深拷貝再到零法則
1. 問題代碼:淺拷貝導致雙重釋放
class BadString {
char* data_;
size_t len_;
public:
BadString(const char* s) : len_(strlen(s)) {
data_ = new char[len_ + 1];
strcpy(data_, s);
}
~BadString() { delete[] data_; }
// 編譯器默認生成:淺拷貝構造/賦值 (逐位複製 data_ 指針)
};
void test() {
BadString a("Hello");
BadString b = a; // 複製構造:b.data_ == a.data_ (同一地址)
// 離開作用域:b 析構 delete[] data_ → a.data_ 成懸空指針
// 再 a 析構 delete[] data_ → **Double Free Crash**
}
2. C++98 風格:手動實現深拷貝 (三法則)
class Cpp98String {
char* data_;
size_t len_;
public:
// 構造
Cpp98String(const char* s = "") : len_(strlen(s)) {
data_ = new char[len_ + 1];
strcpy(data_, s);
}
// 析構
~Cpp98String() { delete[] data_; }
// 複製構造函數 (深拷貝)
Cpp98String(const Cpp98String& other) : len_(other.len_) {
data_ = new char[len_ + 1];
strcpy(data_, other.data_);
}
// 賦值運算符 (複製並交換 / Copy-and-Swap 惯用法)
Cpp98String& operator=(Cpp98String other) { // 參數按值傳遞 → 自動調用複製構造
swap(*this, other); // 交換內部指針
return *this; // other 析構時釋放舊內存
}
friend void swap(Cpp98String& a, Cpp98String& b) noexcept {
std::swap(a.data_, b.data_);
std::swap(a.len_, b.len_);
}
// 訪問器
const char* c_str() const { return data_; }
size_t size() const { return len_; }
};
Copy-and-Swap 優勢:異常安全 (強異常保證)、自動處理自我賦值、代碼復用複製構造邏輯。
3. C++11 移動語義 (五法則)
class Cpp11String {
char* data_;
size_t len_;
public:
// ... 構造/析構/複製構造/複製賦值 同 C++98 ...
// 移動構造函數 (竊取資源)
Cpp11String(Cpp11String&& other) noexcept : data_(other.data_), len_(other.len_) {
other.data_ = nullptr;
other.len_ = 0;
}
// 移動賦值運算符
Cpp11String& operator=(Cpp11String&& other) noexcept {
if (this != &other) {
delete[] data_; // 釋放舊資源
data_ = other.data_; // 竊取
len_ = other.len_;
other.data_ = nullptr;
other.len_ = 0;
}
return *this;
}
// swap 同前
};
noexcept 關鍵:標準容器 (vector 等) 在擴容移動元素時,**僅當移動操作 noexcept 時才使用移動**,否則退化為複製,嚴重影響性能。
4. 現代 C++ 零法則:std::string / std::vector / 智能指針 (推薦)
// 直接用標準庫,零手工內存管理
class ModernString {
std::string data_; // 內部自動管理深拷貝、移動、SSO (小字符串優化)
public:
ModernString(const char* s = "") : data_(s) {}
// 無需聲明任何特殊成員函數!編譯器默認生成的全都正確。
const char* c_str() const { return data_.c_str(); }
size_t size() const { return data_.size(); }
};
// 管理動態數組
class Buffer {
std::vector data_; // 自動深拷貝、移動、擴容
public:
Buffer(size_t n = 0) : data_(n) {}
char* ptr() { return data_.data(); }
size_t size() const { return data_.size(); }
};
// 管理多態對象 / 共享所有權
class Node {
std::shared_ptr next_; // 引用計數,自動釋放
std::unique_ptr data_; // 獨占所有權,移動語義
public:
// 默認複製/移動/析構全正確
};
複製構造 vs 賦值運算符 關鍵差異
| 特性 | 複製構造函數 | 賦值運算符 |
| 調用時機 | 新對象初始化 (T b = a;、參數傳值、返回值) | 已存在對象賦值 (b = a;) |
| 簽名 | T(const T&) / T(T&&) | T& operator=(const T&) / T& operator=(T&&) |
| 需處理自我賦值 | 無 (新對象) | 必須檢查 (if (this != &other)) |
| 需釋放舊資源 | 無 (新對象無資源) | 必須釋放 舊資源再獲取新資源 |
| 返回值 | 無 (構造函數無返回) | *this (引用,支持鏈式賦值) |
| 異常安全 | 構造失敗自動清理 | 需 Copy-and-Swap 或強異常保證 |
現代 C++ 最佳實踐清單
- 零法則優先:成員用
std::string, std::vector, std::unique_ptr, std::shared_ptr, std::optional 等 RAII 類型,**刪除** 手寫的析構/複製/移動函數。
- 必須手寫時 (封裝 C API、特殊資源):
- 聲明 **五個特殊成員函數** (或
= default/= delete)。
- 移動操作 **必須 noexcept**。
- 賦值用 **Copy-and-Swap** (複製賦值) / **竊取並清空源** (移動賦值)。
- 提供
swap 友元函數並 noexcept。
- 禁止複製/移動:
NonCopyable 基類或直接 = delete:
class NonCopyable {
protected: NonCopyable() = default; ~NonCopyable() = default;
public: NonCopyable(const NonCopyable&) = delete; NonCopyable& operator=(const NonCopyable&) = delete;
};
// 或
class MyClass {
MyClass(const MyClass&) = delete;
MyClass& operator=(const MyClass&) = delete;
};
- Rule of Zero 衍生:若基類遵循零法則,派生類**自動**遵循零法則 (除非引入裸指針/資源)。
常見面試陷阱
| 代碼 | 問題 | 解析 |
T a; T b = a; | 調用複製構造 | 初始化非賦值 |
T a; T b; b = a; | 調用賦值運算符 | 先構造 b 再賦值 |
func(T t) { } func(a); | 調用複製構造 | 參數傳值 |
T func() { T t; return t; } | C++17 強制 RVO (返回值優化),**不調用** 複製/移動構造 | 強制省略複製 |
std::vector v; v.push_back(T()); | C++11 移動構造 (若 noexcept),否則複製構造 | 移動語義觸發 |
T& operator=(T other) { swap(*this, other); return *this; } | Copy-and-Swap 惯用法,參數按值傳遞統一複製/移動 | 異常安全、碼簡潔 |
總結
C++ 對象複製/賦值的核心在於**資源所有權轉移語義**。**現代 C++ (C++11/14/17/20) 核心準則:零法則優先 (RAII 類型) → 必須手寫時遵循 五法則 (noexcept 移動 + Copy-and-Swap) → 禁用複製用 = delete**。徹底拋棄 C++98 時代的手動 new/delete/深拷貝樣板代碼,擁抱 std::string, std::vector, unique_ptr, shared_ptr,讓編譯器為你生成正確、高效、異常安全的特殊成員函數。
- 上一篇文章: Java ArrayList 源碼深度解析 (一):核心欄位、建構子與擴容機制
- 下一篇文章: Java 字符串內存分配深度解析:字符串常量池、堆、直接內存、JVM 參數與優化實戰
Copyright ©
程式碼之家 All Rights Reserved