什么是拷贝构造函数?
拷贝构造函数是C++中的一种特殊构造函数,用于创建一个新对象作为现有对象的副本。当我们使用一个对象来初始化同类型的另一个对象时,拷贝构造函数就会被调用。
基本语法
class MyClass {
public:// 拷贝构造函数MyClass(const MyClass& other) {// 拷贝操作}
};
何时调用拷贝构造函数?
拷贝构造函数在以下情况下被调用:
-
对象初始化:
MyClass obj1; MyClass obj2 = obj1; // 拷贝构造函数被调用 MyClass obj3(obj1); // 拷贝构造函数被调用
-
函数参数传递(按值传递):
void doSomething(MyClass obj) {// 函数体 }MyClass myObj; doSomething(myObj); // 拷贝构造函数被调用
-
函数返回值(按值返回):
MyClass createObject() {MyClass obj;return obj; // 可能调用拷贝构造函数(取决于编译器优化) }
默认拷贝构造函数的问题:浅拷贝
如果你没有自定义拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数。这个默认实现执行浅拷贝(成员逐一复制),对于简单类型没问题,但对于指针成员会导致问题:
class ShallowCopy {
private:int* data;int size;
public:ShallowCopy(int sz) : size(sz) {data = new int[size];}// 默认拷贝构造函数执行浅拷贝:// data = other.data; (复制指针值,而不是指向的内容)// size = other.size;~ShallowCopy() {delete[] data;}
};
使用这个类会导致双重释放问题:
ShallowCopy obj1(10);
ShallowCopy obj2 = obj1; // 浅拷贝:两个对象的data指向同一内存
// obj1和obj2析构时都会尝试释放同一块内存 → 崩溃!
解决方案:深拷贝
为了解决浅拷贝的问题,我们需要自定义拷贝构造函数来实现深拷贝:
class DeepCopy {
private:int* data;int size;
public:DeepCopy(int sz) : size(sz) {data = new int[size];}// 自定义拷贝构造函数(深拷贝)DeepCopy(const DeepCopy& other) : size(other.size) {data = new int[size];for (int i = 0; i < size; ++i) {data[i] = other.data[i]; // 复制内容,而不是指针}}~DeepCopy() {delete[] data;}
};
完整的示例代码
#include <iostream>
#include <cstring>class String {
private:char* str;int length;public:// 普通构造函数String(const char* s = "") {length = std::strlen(s);str = new char[length + 1];std::strcpy(str, s);}// 拷贝构造函数(深拷贝)String(const String& other) : length(other.length) {str = new char[length + 1];std::strcpy(str, other.str);std::cout << "拷贝构造函数被调用" << std::endl;}// 析构函数~String() {delete[] str;}// 显示字符串void display() const {std::cout << str << std::endl;}// 修改字符串void update(const char* s) {delete[] str;length = std::strlen(s);str = new char[length + 1];std::strcpy(str, s);}
};int main() {String s1("Hello");String s2 = s1; // 拷贝构造函数被调用std::cout << "s1: ";s1.display();std::cout << "s2: ";s2.display();s1.update("World"); // 修改s1,不影响s2std::cout << "修改后:" << std::endl;std::cout << "s1: ";s1.display();std::cout << "s2: ";s2.display();return 0;
}
拷贝构造函数与赋值运算符的区别
初学者常常混淆拷贝构造函数和赋值运算符,它们的区别在于:
- 拷贝构造函数:创建新对象时使用
- 赋值运算符:已存在的对象被赋予新值时使用
MyClass obj1;
MyClass obj2 = obj1; // 拷贝构造函数
MyClass obj3;
obj3 = obj1; // 赋值运算符
禁止拷贝
有时候我们想禁止对象的拷贝行为,C++11之前可以通过将拷贝构造函数声明为private来实现,C++11之后可以使用delete
关键字:
class NonCopyable {
public:NonCopyable() = default;// 禁止拷贝NonCopyable(const NonCopyable&) = delete;NonCopyable& operator=(const NonCopyable&) = delete;
};
最佳实践
-
三分法则:如果你需要自定义析构函数、拷贝构造函数或拷贝赋值运算符中的任何一个,你可能需要自定义所有这三个。
-
考虑使用智能指针:现代C++中,使用
std::unique_ptr
或std::shared_ptr
可以避免许多手动内存管理的问题。 -
优先使用const引用传递对象:避免不必要的拷贝,提高性能。
-
考虑移动语义:C++11引入了移动构造函数和移动赋值运算符,对于管理资源的类,实现移动语义可以显著提高性能。
总结
拷贝构造函数是C++中管理对象复制行为的重要工具。理解浅拷贝和深拷贝的区别对于编写正确、安全的C++代码至关重要。对于包含动态分配资源的类,几乎总是需要自定义拷贝构造函数来实现深拷贝,避免潜在的内存问题和未定义行为。
在现代C++开发中,随着智能指针和移动语义的引入,我们需要手动实现拷贝构造函数的情况减少了,但理解其原理仍然非常重要。