面試、考試、代碼審查中頻現的 X(const X&) 簽名,正是拷貝構造函數的標誌。許多開發者只知其然不知其所以然,導致淺拷貝 Bug、性能損耗、甚至未定義行為。本文從引用語義、按值傳遞機制出發,層層遞進講透拷貝構造函數。
引用(&)是對象的別名,不是指針,但底層常由指針實現。核心規則:
int& r = a; 必須在聲明時綁定,不可後綁定NULL 引用這些特性使引用成為構造函數參數的完美選擇:避免拷貝、保證有效性、語義清晰。
當對象按值傳遞給函數或從函數返回時:
對於內置類型(int 等),編譯器直接操作寄存器/棧內存;對於類類型,**必須**調用拷貝構造函數。
class X {
public:
X(const X& other); // 標準形式:const 引用參數
// X(X& other); // 非 const 形式(罕見,無法綁定臨時對象)
};
作用: 用一個已存在的同類型對象初始化新對象。
void foo(X x); foo(obj);X bar() { X tmp; return tmp; }X x2 = x1; X x3(x1);注意: X x2 = x1; 是初始化(調用拷貝構造),x2 = x1; 是賦值(調用拷貝賦值運算符),兩者本質不同。
若類未聲明拷貝構造函數,編譯器自動生成一個執行成員級逐位拷貝(bitwise copy / shallow copy)的默認版本。
std::string、std::vector、智能指針)類管理動態資源時,默認淺拷貝會導致:
delete
class String {
char* m_data;
size_t m_len;
public:
String(const char* s = "") {
m_len = strlen(s);
m_data = new char[m_len + 1];
strcpy(m_data, s);
}
// 拷貝構造函數:深拷貝
String(const String& other)
: m_len(other.m_len), m_data(new char[other.m_len + 1]) {
strcpy(m_data, other.m_data);
}
~String() { delete[] m_data; }
// 還需實現拷貝賦值運算符(三/五法則)
};
若類包含其他類對象作為成員(組合),且成員類已正確實現拷貝構造,則:
某些類(如單例、IO 流、線程對象)不應被拷貝:
class NonCopyable {
private:
NonCopyable(const NonCopyable&); // 僅聲明,不定義
NonCopyable& operator=(const NonCopyable&);
};
嘗試拷貝會導致鏈接錯誤(或編譯錯誤,視調用處)。
class NonCopyable {
public:
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
編譯期直接報錯,錯誤信息更清晰。
書中列舉了三種對象初始化場景:
X xx = x;foo(xx);return xx;並指出:用戶定義的拷貝構造函數會在「大多數情況」下被調用,可能產生臨時對象或代碼轉換(如返回值優化 RVO)。
C++11 引入移動構造函數 X(X&&) 和移動賦值運算符,配合拷貝構造、拷貝賦值、析構函數,構成「Rule of Five」:
std::unique_ptr、std::shared_ptr、標準容器管理資源,避免手寫拷貝/移動操作(Rule of Zero)const T&?= delete)?徹底理解拷貝構造函數,是從「會用 C++」邁向「精通 C++ 物件模型」的關鍵一步。