C++ 拷贝构造函数 浅拷贝 深拷贝
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; // 共享指针引用计数+1strcpy(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依然是浅拷贝,只是不报错罢了。