拷贝构造函数是构造函数的一个重载
拷贝构造函数是特殊的构造函数,用于基于已存在对象创建新对象。比如定义一个 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
的拷贝构造函数。若一个类显式实现了析构函数释放资源,通常也需显式写拷贝构造函数,避免资源管理问题。