拷貝構造函數是構造函數的一個重載? ??
拷貝構造函數是特殊的構造函數,用于基于已存在對象創建新對象。比如定義一個?Person
?類:
class Person {
private:std::string name;int age;
public:Person(const std::string& n, int a) : name(n), age(a) {}// 拷貝構造函數Person(const Person& other) : name(other.name), age(other.age) {}
};
這里?Person(const Person& other)
?就是拷貝構造函數,它和普通構造函數?Person(const std::string& n, int a)
?構成重載。
拷貝構造函數的參數要求
其第一個參數必須是類類型對象的引用。如上面?Person
?類的拷貝構造函數?Person(const Person& other)
,other
?就是對?Person
?類型對象的引用。如果用值傳遞,編譯器報錯,因為值傳遞時為創建實參副本會調用拷貝構造函數,引發無窮遞歸調用。
以下解釋不使用值傳遞的原因
正常的拷貝構造函數使用場景
先看一個簡單的類示例,以?Person
?類為例
#include <iostream>
#include <string>class Person {
private:std::string name;int age;
public:Person(const std::string& n, int a) : name(n), age(a) {}// 拷貝構造函數Person(const Person& other) : name(other.name), age(other.age) {std::cout << "拷貝構造函數被調用" << std::endl;}void display() {std::cout << "Name: " << name << ", Age: " << age << std::endl;}
};
在正常情況下,我們可以基于已有的?Person
?對象通過拷貝構造函數來創建新對象,例如:
int main() {Person p1("Alice", 25);Person p2(p1); // 這里調用拷貝構造函數,基于 p1 創建 p2p2.display();return 0;
}
上述代碼中,Person p2(p1);
?語句調用了拷貝構造函數,將?p1
?的數據成員值復制給?p2
,程序能正常運行并輸出?Name: Alice, Age: 25
,同時控制臺會輸出 “拷貝構造函數被調用”。
值傳遞引發的問題
當我們嘗試將?Person
?對象作為函數參數以值傳遞的方式傳遞時,就會出現問題。看下面的代碼:
void printPerson(Person p) {p.display();
}int main() {Person p1("Bob", 30);printPerson(p1); // 嘗試以值傳遞方式傳遞對象return 0;
}
在?printPerson(p1);
?這行代碼中,由于是值傳遞,函數?printPerson
?需要創建一個?Person
?類型的形參?p
?作為實參?p1
?的副本。而創建這個副本的過程,就需要調用?Person
?類的拷貝構造函數。
如果拷貝構造函數的參數也是以值傳遞的方式聲明,比如錯誤地寫成:
class Person {
private:std::string name;int age;
public:Person(const std::string& n, int a) : name(n), age(a) {}// 錯誤的拷貝構造函數聲明,參數為值傳遞Person(Person other) : name(other.name), age(other.age) {std::cout << "拷貝構造函數被調用" << std::endl;}void display() {std::cout << "Name: " << name << ", Age: " << age << std::endl;}
};
當執行?printPerson(p1);
?時,為了創建?printPerson
?函數的形參?p
?作為?p1
?的副本,需要調用拷貝構造函數。但由于拷貝構造函數的參數是值傳遞,又需要為這個參數創建實參的副本,這又會觸發拷貝構造函數的調用,如此循環往復,就形成了無窮遞歸調用。
編譯器會檢測到這種潛在的無窮遞歸情況并報錯,因為這顯然是一個錯誤的邏輯,會導致程序棧溢出等嚴重問題。所以 C++ 規定拷貝構造函數的第一個參數必須是類類型對象的引用(一般是?const
?引用,以避免在拷貝構造過程中意外修改原對象),以防止這種無窮遞歸調用的發生。
自定義類型對象拷貝行為
自定義類型傳值傳參和傳值返回會調用拷貝構造。例如:
Person createPerson() {Person p("Alice", 25);return p;
}
這里?createPerson
?函數返回?Person
?對象?p
?時,會調用?Person
?的拷貝構造函數創建返回值副本。
編譯器自動生成拷貝構造函數
若未顯式定義,編譯器會自動生成。比如:
class Point {
private:int x;int y;
};
編譯器自動生成的拷貝構造函數會對?x
?和?y
?進行值拷貝(淺拷貝),一個字節一個字節地拷貝。如果類包含自定義類型成員變量,會調用該成員變量所屬類的拷貝構造函數。
不同情況是否需顯式實現拷貝構造函數
- 不需要顯式實現的情況:像只含內置類型且無指向資源成員變量的?
Date
?類,編譯器自動生成的拷貝構造函數能滿足需求,無需顯式實現。 - 需要顯式實現的情況:以?
Stack
?類為例,若類中有指針成員變量(如?_a
?指向資源),編譯器自動生成的淺拷貝可能導致問題(如兩個對象的指針指向同一塊內存,釋放時出錯),需實現深拷貝,即自己實現拷貝構造函數,對指向的資源也進行拷貝。 - 特殊情況:對于?
MyQueue
?類,內部主要是自定義類型?Stack
?成員,編譯器自動生成的拷貝構造函數會調用?Stack
?的拷貝構造函數,一般無需顯式實現?MyQueue
?的拷貝構造函數。若一個類顯式實現了析構函數釋放資源,通常也需顯式寫拷貝構造函數,避免資源管理問題。