C++ 的拷贝构造函数(Copy Constructor)是一种特殊的构造函数,用于通过已有对象初始化新创建的对象。它在对象复制场景中起关键作用,尤其在涉及动态内存管理时需特别注意深浅拷贝问题。
一、定义与语法
拷贝构造函数的参数为本类对象的常量引用,格式为 ClassName(const ClassName &src)。若未显式定义,编译器会自动生成默认拷贝构造函数,执行浅拷贝(逐成员复制值)。
二、调用时机
拷贝构造函数在以下场景被调用:
- 对象初始化:通过已有对象显式或隐式创建新对象(如 Student b = a; 或 Student b(a);)。
- 值传递参数:对象以值传递方式传入函数。
- 函数返回值:对象以值传递方式从函数返回。
- 异常处理:抛出或捕获异常对象时可能触发。
三、深浅拷贝问题
1. 浅拷贝的风险
默认拷贝构造函数仅复制成员的值。若类包含指针成员,浅拷贝会导致新对象与原对象指向同一内存地址,析构时引发重复释放错误。
2. 深拷贝的实现
需显式定义拷贝构造函数,为指针成员分配独立内存并复制内容。
四、与赋值运算符的区别
- 拷贝构造函数:在对象创建时调用,用于初始化新对象。
- 赋值运算符:在已存在对象间赋值时调用(如 a = b;),需重载 operator=。
- 关键区别:
Student c = a; // 调用拷贝构造函数(对象c尚未存在)
c = b; // 调用赋值运算符(对象c已存在)
五、最佳实践
- 遵循三法则:若类需自定义拷贝构造函数、析构函数或赋值运算符之一,通常需同时实现三者。
- 禁用拷贝:可通过 = delete 显式删除拷贝构造函数,禁止对象复制(如单例模式)。
- 优化性能:优先使用 const & 传递对象参数,避免不必要的拷贝。
六、浅拷贝与深拷贝对比示例
#include <iostream>
#include <cstring>
using namespace std;
// 浅拷贝导致的问题
class ShallowString {
public:
char* data;
// 构造函数
ShallowString(const char* s) {
data = new char[strlen(s) + 1];
strcpy(data, s);
}
// 默认拷贝构造函数(浅拷贝)
~ShallowString() { delete[] data; }
};
// 深拷贝解决方案
class DeepString {
public:
char* data;
// 构造函数
DeepString(const char* s) {
data = new char[strlen(s) + 1];
strcpy(data, s);
}
// 自定义深拷贝构造函数
DeepString(const DeepString& other) {
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
}
// 析构函数
~DeepString() { delete[] data; }
};
int main() {
// 浅拷贝问题演示
ShallowString a("Hello");
ShallowString b = a; // 默认浅拷贝
cout << "浅拷贝结果: " << a.data << " | " << b.data << endl;
// 运行时崩溃:a和b析构时重复释放同一内存
// 深拷贝解决方案
DeepString c("World");
DeepString d = c; // 调用深拷贝构造函数
strcpy(c.data, "C++"); // 修改原对象不影响副本
cout << "深拷贝结果: " << c.data << " | " << d.data << endl;
return 0;
}
输出(浅拷贝运行崩溃,深拷贝输出):
浅拷贝结果: Hello | Hello
深拷贝结果: C++ | World
关键点:
- 浅拷贝类未定义拷贝构造函数,导致重复释放内存
- 深拷贝类显式分配独立内存,确保对象独立性
七、拷贝构造函数调用时机示例
#include <iostream>
using namespace std;
class Logger {
public:
int id;
// 构造函数
Logger(int x) : id(x) {
cout << "构造对象" << id << endl;
}
// 拷贝构造函数
Logger(const Logger& src) : id(src.id + 100) {
cout << "拷贝构造对象" << id << endl;
}
};
// 值传递触发拷贝构造
void process(Logger obj) {
cout << "处理对象" << obj.id << endl;
}
// 返回值触发拷贝构造
Logger create() {
Logger temp(999);
return temp; // 触发拷贝构造(编译器可能优化)
}
int main() {
Logger a(1); // 调用普通构造函数
Logger b = a; // 调用拷贝构造函数[3](@ref)
process(a); // 值传递触发拷贝构造[4](@ref)
Logger c = create(); // 返回值触发拷贝构造
return 0;
}
构造对象1
拷贝构造对象101
拷贝构造对象101
处理对象101
构造对象999
拷贝构造对象1099
Logger b = a 显式初始化触发拷贝
process(a) 值传递参数触发拷贝
返回值时编译器可能优化(RVO),但逻辑上仍存在拷贝过程
八、完整深拷贝类示例(含赋值运算符)
#include <iostream>
#include <cstring>
using namespace std;
class Vector {
private:
int* arr;
size_t size;
public:
// 构造函数
Vector(size_t s) : size(s) {
arr = new int[size]{0};
cout << "构造Vector(大小" << size << ")" << endl;
}
// 深拷贝构造函数
Vector(const Vector& src) : size(src.size) {
arr = new int[size];
memcpy(arr, src.arr, size * sizeof(int));
cout << "深拷贝Vector" << endl;
}
// 赋值运算符重载
Vector& operator=(const Vector& rhs) {
if (this != &rhs) {
delete[] arr; // 释放原有资源
size = rhs.size;
arr = new int[size];
memcpy(arr, rhs.arr, size * sizeof(int));
cout << "赋值运算符调用" << endl;
}
return *this;
}
// 析构函数
~Vector() {
delete[] arr;
cout << "销毁Vector" << endl;
}
void set(int index, int value) { arr[index] = value; }
void print() {
cout << "[ ";
for (size_t i = 0; i < size; ++i)
cout << arr[i] << " ";
cout << "]" << endl;
}
};
int main() {
Vector v1(3);
v1.set(0, 10);
v1.set(1, 20);
v1.set(2, 30);
Vector v2 = v1; // 调用拷贝构造函数
v2.set(1, 99); // 修改副本不影响原对象
Vector v3(2);
v3 = v1; // 调用赋值运算符
cout << "v1: ";
v1.print(); // [10 20 30]
cout << "v2: ";
v2.print(); // [10 99 30]
cout << "v3: ";
v3.print(); // [10 20 30]
return 0;
}
构造Vector(大小3)
深拷贝Vector
构造Vector(大小2)
赋值运算符调用
v1: [ 10 20 30 ]
v2: [ 10 99 30 ]
v3: [ 10 20 30 ]
销毁Vector
销毁Vector
销毁Vector
设计原则:
- 遵循三法则:同时实现拷贝构造、析构和赋值运算符
- 自赋值检查:if (this != &rhs) 防止资源错误释放
- 深拷贝保证:memcpy复制数据而非指针地址
九、禁用拷贝的示例
class NonCopyable {
public:
NonCopyable() = default;
// 删除拷贝构造函数和赋值运算符
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
int main() {
NonCopyable obj1;
// NonCopyable obj2 = obj1; // 编译错误
// obj1 = NonCopyable(); // 编译错误
return 0;
}
应用场景:单例模式、资源独占类等需要禁止复制的场景
十、浅拷贝何深拷贝的本质区别是什么
浅拷贝仅复制指针地址,深拷贝递归复制所有数据到独立内存
十一、使用智能指针避免手动管理
#include <iostream>
#include <memory>
using namespace std;
class SafeString {
public:
shared_ptr<char[]> data; // 共享指针自动管理内存
// 构造函数
SafeString(const char* s) {
data = make_shared<char[]>(strlen(s) + 1);
strcpy(data.get(), s);
cout << "构造 SafeString: " << data.get() << endl;
}
// 无需定义拷贝构造函数和析构函数!
};
int main() {
SafeString s1("Modern C++");
SafeString s2 = s1; // 共享指针引用计数+1
strcpy(s1.data.get(), "Updated");
cout << "s1.data: " << s1.data.get() << endl;
cout << "s2.data: " << s2.data.get() << endl;
return 0; // 自动释放内存,无崩溃
}
构造 SafeString: Modern C++
s1.data: Updated
s2.data: Updated // 共享同一内存但无双重释放风险
关键点:
- 使用 shared_ptr 自动管理内存生命周期
- 适用于资源共享场景,无需手动实现深拷贝
注意:
虽然使用了shared_ptr,但是SafeString s2 = s1依然是浅拷贝,只是不报错罢了。