You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++自定义String类析构函数两次调用问题咨询及内存安全性疑问

关于C++析构函数调用两次的问题解答

嘿,我来帮你理清这个问题——先直接给你核心结论,再一步步拆解你的代码问题和解决方案。

核心疑问:析构函数调用两次≠内存错误

首先要明确:析构函数被调用两次本身不是bug,关键是看每次调用是否在释放自己合法拥有的内存。只要不同对象释放的是各自分配的内存块,没有出现同一块内存被多次delete的情况,程序就是安全的。

你用unique_ptr后虽然析构还是调用两次,但因为unique_ptr严格保证了内存的唯一所有权,不会让多个对象指向同一块堆内存,所以不会触发double free这类致命错误,这是完全正常的。

你的代码里两次析构的来源

先看你的主函数:

int main() {
    String a = "asdc";
    a = "ads";
    cin.get();
}

这两次析构调用其实来自两个不同的对象:

  1. 第一次析构:当执行a = "ads"时,字符串字面量"ads"会被临时构造为一个String对象(调用你的String(const char*)构造函数)。赋值操作完成后,这个临时对象就没用了,会被自动销毁,触发第一次~String()调用。
  2. 第二次析构:main函数执行结束时,局部变量a离开作用域,被销毁,触发第二次析构。

之前没加空构造函数时不是没这个情况,只是可能你没注意到临时对象的析构输出而已——临时对象的创建和销毁一直都存在。

但这里还有个隐藏的严重问题:你的String类没有重载赋值运算符operator=!当你执行a = "ads"时,编译器会自动生成一个默认的赋值运算符,它只会做浅拷贝:把临时对象的m_Buffer指针直接复制给a.m_Buffer。这会导致两个问题:

  • 原a的m_Buffer指向的内存没有被释放,直接造成内存泄漏。
  • 临时对象销毁时会delete[]这个指针,之后a的m_Buffer就变成了野指针;当a销毁时再次delete[]这个野指针,就会触发double free错误!

你说用unique_ptr解决了问题,本质是unique_ptr禁止了拷贝赋值(默认删除了拷贝构造和拷贝赋值),或者你用了移动语义——它通过独占内存所有权的方式,从根源上避免了浅拷贝带来的重复释放问题。

用五五法则(Rule of Five)修复你的String类

因为你的类管理了堆内存(m_Buffer),所以必须手动实现C的五五法则(C11及以后),也就是五个特殊成员函数:

  1. 构造函数(你已经实现了)
  2. 析构函数(你已经实现了)
  3. 拷贝构造函数(你已经实现了)
  4. 拷贝赋值运算符(你缺失的关键)
  5. 移动构造函数(可选但能提升效率)
  6. 移动赋值运算符(可选但推荐)

给你补上拷贝赋值运算符的实现:

String& operator=(const String& other) {
    // 先处理自赋值的边界情况
    if (this == &other) return *this;
    
    // 先释放当前对象的旧内存,避免泄漏
    delete[] m_Buffer;
    
    // 分配新内存并拷贝数据
    m_Size = other.m_Size;
    m_Buffer = new char[m_Size + 1];
    memcpy(m_Buffer, other.m_Buffer, m_Size + 1);
    
    return *this;
}

如果想支持更高效的移动语义(避免临时对象的内存拷贝),可以添加移动构造和移动赋值:

// 移动构造函数:接管源对象的内存
String(String&& other) noexcept : m_Buffer(other.m_Buffer), m_Size(other.m_Size) {
    // 把源对象置为安全状态,防止它析构时释放我们刚接管的内存
    other.m_Buffer = nullptr;
    other.m_Size = 0;
}

// 移动赋值运算符
String& operator=(String&& other) noexcept {
    if (this == &other) return *this;
    
    delete[] m_Buffer;
    
    m_Buffer = other.m_Buffer;
    m_Size = other.m_Size;
    
    other.m_Buffer = nullptr;
    other.m_Size = 0;
    
    return *this;
}

这样修改后,当你执行a = "ads"时,临时对象会被移动赋值给a,既不会内存泄漏,也不会出现野指针和重复释放的问题。

最后总结一下

  • 析构函数调用两次是正常场景(比如临时对象的销毁),只要每个对象只释放自己合法拥有的内存,就不会有问题。
  • 管理堆内存的自定义类必须遵循五五法则,否则会出现内存泄漏、double free等经典内存错误。
  • unique_ptr是C++标准库提供的智能指针,它通过独占内存所有权的方式帮你自动管理内存,避免手动操作原始指针的各种坑,是替代原始指针的优先选择。

内容的提问来源于stack exchange,提问作者Mustafa Alzuriqi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 16:29:05