在 C++ 开发中,对象复制是日常编程的常见需求。无论是传递大型容器、处理资源管理,还是实现自定义类型的拷贝构造函数,了解浅拷贝(shallow copy)与深拷贝(deep copy)的区别及其实现方式,都是提升代码质量与性能的关键。本文将从概念出发,结合实战案例,系统阐述浅拷贝与深拷贝在 C++ 中的实现技巧与最佳实践。
一、浅拷贝与深拷贝的定义
- 浅拷贝(Shallow Copy):复制对象时仅复制其成员的值,对象内部的指针或引用被复制时,指向的是同一块内存。结果是两个对象共享同一份资源,修改一方会影响另一方。浅拷贝在默认拷贝构造函数与赋值运算符中使用,C++ 编译器会自动生成。
- 深拷贝(Deep Copy):复制对象时不仅复制成员的值,还会递归复制所引用的资源,生成独立的内存副本。深拷贝确保两个对象互不影响,适用于拥有指针、动态分配数组、文件句柄等资源的类。
二、典型场景对比
| 场景 | 需要浅拷贝 | 需要深拷贝 |
|---|---|---|
| 标准容器(如 `std::vector | ||
| `) | 默认拷贝即可 | 无需深拷贝,容器内部已实现深拷贝 |
| 自定义 POD 结构体 | 只要无指针成员 | 无需 |
| 自定义包含裸指针的类 | 可能导致悬垂指针 | 必须实现深拷贝 |
| 需要共享资源(如多线程共享同一共享内存) | 可使用浅拷贝 + 原子操作 | 无需深拷贝 |
三、实现深拷贝的关键技巧
-
显式管理资源
在类中使用new/delete时,必须显式提供拷贝构造函数、赋值运算符和析构函数,遵循 Rule of Three / Rule of Five。class Buffer { public: Buffer(size_t n) : size(n), data(new int[n]) {} Buffer(const Buffer& other) : size(other.size), data(new int[other.size]) { std::copy(other.data, other.data + other.size, data); } Buffer& operator=(const Buffer& other) { if (this != &other) { delete[] data; size = other.size; data = new int[size]; std::copy(other.data, other.data + size, data); } return *this; } ~Buffer() { delete[] data; } private: size_t size; int* data; }; -
使用智能指针
std::unique_ptr或std::shared_ptr能自动管理资源,减少手动错误。class Node { public: std::shared_ptr <Node> left; std::shared_ptr <Node> right; }; // 复制时,默认复制指针引用计数,不需要手动实现深拷贝。 -
利用标准库算法
std::copy、std::transform等可以简化复制逻辑,保持代码简洁。 -
自定义拷贝控制
对于需要部分浅拷贝、部分深拷贝的复杂对象,使用clone()虚函数或工厂模式,统一拷贝行为。
四、浅拷贝的陷阱与防范
- 悬垂指针:复制后原对象析构,指针仍指向已释放内存。
- 双重释放:两个对象持有同一指针,析构时会多次
delete。 - 线程安全:浅拷贝导致共享资源时,多线程访问需同步。
防范策略
- 尽量避免裸指针,改用智能指针或容器。
- 使用
std::move语义避免不必要的浅拷贝。 - 对于需要共享的资源,明确使用
std::shared_ptr并记录所有者。
五、实战案例:实现一个自定义字符串类
#include <cstring>
#include <iostream>
class MyString {
public:
MyString(const char* s = "") : data(new char[strlen(s)+1]) {
std::strcpy(data, s);
}
// 深拷贝
MyString(const MyString& other) : data(new char[std::strlen(other.data)+1]) {
std::strcpy(data, other.data);
}
// 右值拷贝(移动)
MyString(MyString&& other) noexcept : data(other.data) {
other.data = nullptr;
}
MyString& operator=(const MyString& other) {
if (this != &other) {
delete[] data;
data = new char[std::strlen(other.data)+1];
std::strcpy(data, other.data);
}
return *this;
}
MyString& operator=(MyString&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
other.data = nullptr;
}
return *this;
}
~MyString() { delete[] data; }
const char* c_str() const { return data; }
private:
char* data;
};
int main() {
MyString a("Hello");
MyString b = a; // 深拷贝
MyString c = std::move(a); // 移动,a 失效
std::cout << b.c_str() << "\n" << c.c_str() << "\n";
}
该实现演示了:
- 深拷贝:拷贝构造函数与赋值运算符都创建新的字符数组。
- 移动语义:右值拷贝避免不必要的内存分配,提高性能。
六、结语
浅拷贝与深拷贝是 C++ 对象复制的两种基本策略。
- 当对象仅包含值类型成员或已有容器/智能指针时,默认浅拷贝已足够。
- 当对象持有动态资源或需要独立副本时,必须实现深拷贝,遵循 Rule of Three/Five。
掌握两者的区别与实现技巧,可有效避免内存泄漏、悬垂指针等典型错误,让 C++ 代码更健壮、更易维护。