深拷贝不是调用std::string拷贝构造函数就行,需手动实现裸指针成员的内存分配、复制与释放;std::string自身已做深拷贝(含SSO优化),但无法代管用户自定义的char*;MyString类须显式编写拷贝构造、赋值运算符及析构函数,并处理自赋值、异常安全与资源管理。

深拷贝不是调用 std::string 的拷贝构造函数就行了吗?
不是。默认情况下,std::string 确实会做深拷贝——但它只在你用它自己管理字符串时才可靠。一旦你手动用 new char[] 分配内存、用裸指针保存字符数据,或者封装了 char* 成员变量,深拷贝就完全得你自己负责。
典型陷阱:写了 MyString 类但没写拷贝构造函数和赋值运算符,导致两个对象共享同一块堆内存,一删全崩。
- 只要类里有裸指针(比如
char* data),就必须显式实现深拷贝逻辑 -
std::string内部已做 PIMPL 或小字符串优化,它的拷贝是安全的,但不能帮你救你的char* - 如果用了
std::vector<char></char>或std::string作成员,编译器生成的默认拷贝就是深拷贝,无需额外操作
怎么写一个带深拷贝的 MyString 类?
核心是三步:分配新内存、逐字拷贝、更新长度/容量。别漏掉析构函数释放旧内存,否则必泄漏。
class MyString {
char* data_;
size_t len_;
public:
MyString(const char* s = nullptr) : len_(s ? strlen(s) : 0) {
data_ = new char[len_ + 1];
if (s) strcpy(data_, s);
else data_[0] = '\0';
}
<pre class="brush:php;toolbar:false;">// 深拷贝构造函数
MyString(const MyString& other) : len_(other.len_) {
data_ = new char[len_ + 1];
strcpy(data_, other.data_);
}
// 深拷贝赋值运算符(注意自赋值检查)
MyString& operator=(const MyString& other) {
if (this == &other) return *this;
delete[] data_;
len_ = other.len_;
data_ = new char[len_ + 1];
strcpy(data_, other.data_);
return *this;
}
~MyString() { delete[] data_; }};
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 必须检查自赋值,否则
delete[] data_后再读other.data_就是野指针 - 别用
memcpy替代strcpy,除非你确认源数据以'\0'结尾且长度准确 - 如果支持 move 语义,建议加上移动构造和移动赋值,避免不必要的堆分配
为什么用 std::string 就不用操心深拷贝?
因为 std::string 的拷贝构造函数和赋值运算符内部已经做了深拷贝(C++11 起基本保证,除非启用了 SSO 且字符串很短——但这对用户透明,行为仍等价于深拷贝)。
你写 std::string a = "hello"; std::string b = a;,b 拥有独立内存,改 b 不影响 a,也不用管析构。
- SSO(Small String Optimization)下,短字符串存在对象内部,不走堆;长字符串才分配堆内存并深拷贝
- 所有标准库容器(
std::vector、std::map等)对std::string的存储/复制都自动适配这套机制 - 跨线程传递
std::string也安全,因为每个副本都是独立的
容易被忽略的深拷贝边界情况
深拷贝不是“多分配一次内存”就完事,还要考虑异常安全、资源生命周期和嵌套结构。
- 构造函数里
new失败抛出std::bad_alloc,此时对象还没建好,不会调用析构,但已有部分成员可能已初始化——需用 try/catch 或 RAII 封装分配 - 如果类里还有其他资源(如文件句柄、socket),深拷贝必须一并复制或重新打开,不能只拷字符串
- 含
std::unique_ptr成员时,拷贝要显式 clone 其指向内容,否则默认行为是 move(编译不过) - 继承体系中,基类若含裸指针,派生类的拷贝构造函数必须显式调用基类深拷贝逻辑,不能只靠
Base(other)—— 那只是默认浅拷贝
深拷贝真正难的不是代码几行,而是判断哪些东西“值得”且“需要”深拷——有时候共享引用更合理,强行深拷反而浪费内存和时间。

















