拷贝构造函数在新对象创建时调用,赋值运算符在已有对象上修改内容;前者用于初始化(如String s2(s1)),后者要求左操作数已存在(如s3 = s1);二者参数、返回值签名严格固定,且深拷贝需注意空指针、异常安全及裸指针兜底。

区别不在语法或写法上,而在对象生命周期的“生死状态”——拷贝构造函数只在新对象诞生时调用,赋值运算符只在已有对象活着时修改它。
拷贝构造函数只在创建新对象时触发
它不是“复制”,而是“出生”。只要语句中产生了尚未存在过的对象实例,就走这里。
-
String s2(s1)、String s3 = s1:声明即初始化,调用拷贝构造 - 函数按值传参:
void f(String s) { ... }中f(s1)会构造形参s - 函数按值返回:
String create() { return String("hi"); }中String x = create()(未启用RVO时)会用返回值构造x
注意:String& 或 const String& 传参不触发拷贝构造;移动语义启用后(C++11+),临时对象可能走移动构造而非拷贝构造。
赋值运算符只对已存在的对象生效
左边操作数必须是已经完成构造、尚未析构的对象。它不创建内存,只改写内容。
立即学习“C++免费学习笔记(深入)”;
-
s3 = s1:s3已存在(比如先写了String s3;),才调用operator= -
s3 = create():右边是临时对象,但左边s3是旧对象,仍走赋值路径(除非编译器优化为移动赋值) - 必须检查自赋值:
if (this == &other),否则delete[] m_data后再读other.m_data会崩溃
默认生成的赋值运算符不做资源清理,浅拷贝指针会导致双重释放——这是最常引发段错误的根源之一。
参数和返回值签名差异直接影响行为
这两个函数签名不能随便改,改错就会编译失败或逻辑错乱。
- 拷贝构造函数形参必须是
const MyClass&:若写成MyClass(值传递),会无限递归调用自己导致栈溢出 - 赋值运算符返回类型必须是
MyClass&:支持链式赋值如a = b = c;返回void或值类型会切断引用链,且无法配合某些泛型算法 - 两者都不能是
static,也不能是explicit(拷贝构造可显式声明为explicit,但会禁用隐式转换,极少这么做)
深拷贝实现里最容易漏掉的关键点
写完 new 和 delete[] 不代表就安全了。真正容易翻车的是边界和异常安全。
- 分配内存前没判空:
other.m_data可能是nullptr,strlen(nullptr)是未定义行为 - 分配失败没处理:如果
new char[...]抛异常,原对象已被delete[],变成半残状态(建议用 copy-swap 惯用法) - 忘记处理 const 成员或引用成员:它们不能在赋值中被修改,但拷贝构造可以初始化——这决定了有些类根本不能支持赋值
最隐蔽的问题是:当类里有 std::vector 或 std::string 这类自带深拷贝语义的成员时,你反而不需要手动写深拷贝逻辑;但一旦混入裸指针,整个类的拷贝语义就全得你兜底——这点很多人直到 core dump 才意识到。


















