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

C++ 對象模型深度解析:拷貝構造函數的合成與語義

     編輯:關於C++

前言

對於一個未曾實例化的空類別,編譯器不會為其生成任何預設函數;但一旦實例化,編譯器便會按需合成相應的函數,主要包括:預設構造函數、拷貝構造函數、析構函數與賦值運算符。本文將聚焦於「拷貝構造函數」,深入探討其觸發時機、分類(trivial 與 non-trivial)、以及編譯器在何種情況下會合成非平凡的拷貝構造函數。

拷貝構造函數的三大觸發場景

拷貝構造函數的簽名通常為 ClassName(const ClassName&),當一個對象以另一個同類對象作為初始值時被調用。具體發生在以下三種情況:

  1. 顯式初始化:Animal a2 = a1;
  2. 函數參數按值傳遞:void func(Animal a); func(a1);
  3. 函數按值返回對象:Animal func() { Animal a; return a; }

位逐次拷貝與淺拷貝

若類別未聲明拷貝構造函數,且不包含需要特殊處理的成員,編譯器採用「位逐次拷貝」,即按位複製內存內容。這屬於淺拷貝,對於包含裸指針的類別極不安全,會導致雙重釋放或資源洩漏。

淺拷貝示意圖

相比之下,std::string 等類別提供了顯式拷貝構造函數,會為新對象重新分配內存並複製數據,實現深拷貝,避免雙指針指向同一塊內存。

Trivial 與 Non-trivial 拷貝構造函數

  • Trivial:直接進行位逐次拷貝,效率高但語義受限。
  • Non-trivial:編譯器合成或用戶定義,執行自定義邏輯(如調用成員/基類的拷貝構造函數、調整虛表指針等)。

編譯器合成 Non-trivial 拷貝構造函數的四種情況

1. 含有拷貝構造函數的成員類對象

若類別包含擁有拷貝構造函數的成員對象,編譯器合成的拷貝構造函數會逐一調用各成員的拷貝構造函數,而非簡單位拷貝。

class Animal {
public:
    Animal() {}
    Animal(const Animal&) { cout << "Animal's Copy Constructor" << endl; }
};

class Dog {
    Animal animal; // 含有拷貝構造函數的成員
};
Dog d1;
Dog d2 = d1; // 輸出 "Animal's Copy Constructor"

2. 繼承自帶有拷貝構造函數的基類

派生類的合成拷貝構造函數會先調用基類的拷貝構造函數,再處理自身成員。

class Dog : public Animal {
    // ...
};
Dog d1;
Dog d2 = d1; // 同樣觸發基類拷貝構造函數

3. 含有虛函數的類

虛函數引入虛表指針。同類型對象間拷貝時,虛表指針指向同一虛表,位逐次拷貝安全,編譯器不會合成拷貝構造函數。

Dog d1;
Dog d2 = d1; // vptr 相同,位拷貝即可

但派生類對象初始化基類對象時,基類需重新設定 vptr 指向自身虛表,此時位拷貝失效,編譯器必須合成 non-trivial 拷貝構造函數。

Dog d;
Animal a = d; // 必須調整 vptr,合成拷貝構造函數

4. 含有虛基類的派生類

虛繼承引入虛基類指針。拷貝時需重新設定指向虛基類的指針,位逐次拷貝失效,編譯器合成拷貝構造函數處理指針調整。

虛繼承結構圖

總結

編譯器合成 non-trivial 拷貝構造函數的四大場景:

  1. 成員類對象擁有拷貝構造函數
  2. 基類擁有拷貝構造函數
  3. 含虛函數且發生「派生類到基類」的初始化
  4. 含虛基類的派生類對象拷貝

關鍵點:同類型對象間拷貝,即使有虛函數或虛基類,仍採用位逐次拷貝,不合成拷貝構造函數。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved