浅拷贝

在 C++ 中,如果没有显式定义拷贝构造函数和拷贝赋值运算符,编译器会自动生成默认版本。这个默认版本执行的是 浅拷贝,也称为按位拷贝(bitwise copy)。

核心概念:浅拷贝

逐字节复制对象的所有非静态成员。对于指针成员,复制的是指针本身(即内存地址),而不是指针所指向的数据。

示例:

假设有一个 String 类,包含一个 char* m_data 指针。当创建对象 s1("Hello") 时,m_data 指向动态分配的堆内存。如果使用默认拷贝构造创建 s3(s1),那么 s3.m_data 会被设置为与 s1.m_data 相同的地址,两者指向同一块内存。

“如果只是拷贝,那就是指针拷贝过来,他们两个其实指到同一个地方。这并不是真正的拷贝。” 为什么称为“浅拷贝”?因为它只复制了表面的一层(对象本身的成员),真正的拷贝(深拷贝)应为新对象分配独立的内存

为什么对于复数类没问题?

对于不含指针的类(如复数类,含 real 和 imag 两个 double 成员),按位复制即可得到两个完全独立的对象。编译器默认的拷贝行为在这种情况下是足够的。

浅拷贝的严重后果

当多个对象的指针指向同一块堆内存时,会发生一系列连锁灾难。

后果一:析构时的双重释放

假设 s1s3 共享同一块存储 "Hello"的内存。当 s1 析构时,其析构函数会 delete m_data,释放那块内存。此时 s3.m_data 变成了一个悬空指针。接着 s3 析构时,再次对同一个地址调用 delete,从而导致双重释放,这是未定义行为,通常导致程序崩溃或数据损坏。

后果二:内存泄漏

在赋值操作中(拷贝赋值),如果对象原本拥有自己的内存,使用默认的拷贝赋值运算符会导致原来拥有的内存没有被释放,从而泄漏。例如:s3 原本指向 "World"(已分配内存)。当执行 s3 = s1(默认拷贝赋值)时,编译器按位复制 s1.m_datas3.m_data,导致 s3.m_data 指向 s1 的内存,而原来 s3指向的 "World" 内存再也无法访问,造成内存泄漏。

只要类中包含指针成员并动态管理内存,就必须自定义拷贝构造函数、拷贝赋值运算符和析构函数(Big Three),以实现深拷贝,确保每个对象拥有独立的内存副本。