编程C++开发者

解释一下浅拷贝和深拷贝在动态内存类中的区别。什么情况下需要深拷贝构造函数?如何手动实现深拷贝?

用 Hintsage AI 助手通过面试

答案。

问题的背景

在 C++ 中,复制对象时最初使用的是逐成员复制机制:针对对象的每个成员调用其相应的复制操作。对于内置类型这是安全的,但对于动态资源则会出现问题:只是复制了指针,而不是实际数据。

问题

如果一个对象包含指向堆内存的指针,那么复制两个对象后,它们将指向同一内存区域。然后,当一个对象被销毁时,内存将被释放,第二个对象的指针将变得无效(“野指针”)。这会导致运行时错误和内存泄漏。

解决方案

为了使复制独立,需要进行深拷贝——逐字节复制并分配自己的缓冲区。这可以通过编写自定义的复制构造函数和赋值运算符来实现。

示例代码:

class MyString { char* data; public: MyString(const char* s) { data = new char[strlen(s)+1]; strcpy(data, s); } // 深拷贝构造函数 MyString(const MyString& src) { data = new char[strlen(src.data) + 1]; strcpy(data, src.data); } // 深拷贝赋值 MyString& operator=(const MyString& src) { if (this != &src) { delete[] data; data = new char[strlen(src.data) + 1]; strcpy(data, src.data); } return *this; } ~MyString() { delete[] data; } };

关键特性:

  • 对于管理外部资源的类是必需的
  • 需要实现自己的复制构造函数和operator=
  • 如果不正确实现“三个法则”,可能会导致内存泄漏

带陷阱的问题。

如果类只包含指针,但不分配内存,为什么还需要自定义析构函数?

析构函数仅在您在类中明确分配了内存(或其他资源)时才是必要的。如果指针没有分配内存,默认的析构函数就足够了。


如果在包含动态内存的类中声明了复制构造函数,但未实现operator=会发生什么?

如果您手动定义了复制构造函数,编译器不会自动实现operator=;它将被隐式声明,或者编译器将发出错误/警告(取决于标准)。这将导致赋值时行为不明确:将执行逐成员复制,并出现双重释放或内存泄漏。

示例代码:

MyString a("hi"); MyString b = a; // 可以:您的复制构造函数 MyString c("bye"); c = a; // 问题!如果不手动实现operator=将发生浅拷贝

在手动实现operator=时,自我赋值有什么危险?

如果在不检查this!=&rhs的情况下共享资源,那么在赋值给自己时将执行delete[] data,随后会尝试复制已经被销毁的数组,这会导致segfault。自我保护:总是检查自我赋值。

if (this != &rhs) { ... }

常见错误和反模式

  • 未实现“三个法则”中的一种方法(复制构造函数,operator=,析构函数)
  • 未检查自我赋值
  • 忘记delete[]造成的内存泄漏
  • 如果指针是共享的则双重释放

生活中的示例

负面案例

开发者复制了一个带有内置指针的类对象,并未实现深拷贝。复制后多个对象共享同一内存区域。两个析构函数导致双重删除,程序崩溃。

优点:

  • 写代码很简单(“表面上拷贝工作正常”)

缺点:

  • 程序崩溃,无法预测的bug
  • 内存泄漏或双重释放

积极案例

开发者正确实现了复制构造函数、赋值运算符和析构函数。每个对象拥有自己的内存。

优点:

  • 拷贝和销毁时的安全性
  • 没有内存泄漏

缺点:

  • 当有大量拷贝时,拷贝开销增加