默认赋值运算符执行浅拷贝,即逐成员复制值,对指针仅复制地址而不复制所指数据,导致多个对象共享同一堆内存,易引发双重释放、悬空指针和内存泄漏。

默认情况下,obj2 = obj1 会调用编译器生成的默认赋值运算符,执行浅拷贝 —— 成员变量逐个复制,但指针成员只复制地址值,不复制它指向的数据。这在含动态内存(如 char*、int*)的类中极易引发双重释放、悬空指针或内存泄漏。
默认赋值运算符做了什么
编译器自动生成的 operator= 是一个 public inline 成员函数,行为等价于手动对每个非静态数据成员做“位拷贝”:
- 基本类型(
int、double)和标准容器(std::string、std::vector)安全复制 - 指针成员(如
char* data)仅复制地址,obj1.data和obj2.data指向同一块堆内存 - 不会调用任何成员对象的赋值运算符(除非该成员类型重载了
operator=) - 不调用析构函数,也不分配新内存
为什么含指针的类必须重载 operator=
看这个典型崩溃场景:
class BadString {
public:
char* data;
BadString(const char* s) {
data = new char[strlen(s)+1];
strcpy(data, s);
}
~BadString() { delete[] data; }
};
BadString a("hello");
BadString b;
b = a; // 默认 operator=:b.data = a.data
// 析构时:a.~BadString() → delete[] "hello"
// b.~BadString() → delete[] "hello" ← 再次释放同一地址,UB
重载时必须处理四个关键点:
- 检查自赋值:
if (this == &other) return *this; - 释放当前资源:
delete[] data; - 深拷贝源数据:
data = new char[len+1]; strcpy(data, other.data); - 返回引用:
return *this;(支持c = b = a)
operator= 和拷贝构造函数的根本区别
二者语义不同,触发时机完全不同:
-
String a = b;或String c(b);→ 调用拷贝构造函数(创建新对象) -
String d; d = b;→ 调用operator=(修改已有对象) - 即使写法像赋值(
String c = b;),只要c是首次定义,就不是赋值,而是拷贝初始化 - 拷贝构造函数无须清理旧资源,
operator=必须先清理this已持有的资源
最常被忽略的是:一旦类需要自定义拷贝构造函数(比如有指针成员),几乎一定也要自定义 operator= —— 否则对象生命周期中某次看似普通的赋值,就会悄悄埋下崩溃种子。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











