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

C++:返回含指针的对象时如何避免析构引发的内存崩溃

问题分析与解决

代码示例

#include<iostream>
class A
{
public:
    int* a;
    char name;
    A(char _n = 'N')
    {
        a = 0;
        name = _n;
    }
    A(int _a, int _b, char _n = 'N')
    {
        name = _n;
        a = new int[2]{ _a, _b };
    }
    ~A()
    {
        std::cout << "deleting object..." << name << "\n";
        delete[] a;
    }
    //
    void operator=(A b)
    {
        std::cout << "cleanup begin\n";
        delete[] a;
        a = new int[] {b.a[0], b.a[1]};
    }
    //
    A Copy()
    {
        if (a == 0) return *new A();
        A* _r = new A(a[0], a[1], name + 1);
        return *_r;
    }
};

//

int main()
{
    A d{0, 1, 'T'};
    {
        A Z(0, 1);
        Z = Z.Copy();
    }
    std::cout << "check\n";
    return 0;
}

核心问题

执行Z = Z.Copy()触发delete_scalar断点,本质是代码存在两处致命问题:

  1. Copy()函数的内存错误:用new创建对象后返回解引用值,不仅会造成内存泄漏(new的对象永远无法释放),还会因为默认拷贝构造的浅拷贝,让临时对象和动态分配的原对象共享同一块内存。临时对象销毁时释放内存,后续Z的指针访问该内存会引发非法操作。
  2. 赋值运算符的浅拷贝与重复释放:赋值运算符参数采用值传递,会通过默认拷贝构造创建参数b,b和传入的临时对象共享a指针。赋值结束后b销毁时释放内存,Z析构时再次释放同一块内存,触发重复释放断点。同时代码未处理自赋值场景,若出现Z=Z会先释放自身内存,再访问已释放的指针。

解决步骤

1. 修复Copy()函数

无需动态分配对象,直接返回栈对象即可,编译器会通过返回值优化(RVO)避免冗余拷贝:

A Copy()
{
    if (a == nullptr) return A();
    return A(a[0], a[1], name + 1);
}

2. 实现类的「三法则」

因类持有动态内存,必须自定义拷贝构造、拷贝赋值运算符,配合已有的析构函数,彻底解决浅拷贝问题:

  • 自定义拷贝构造函数:
A(const A& other)
{
    name = other.name;
    if (other.a == nullptr) {
        a = nullptr;
    } else {
        a = new int[2]{other.a[0], other.a[1]};
    }
}
  • 修复赋值运算符:
    推荐用「拷贝交换法」,简洁且自动处理自赋值和异常安全:
// 先实现交换函数
void swap(A& other) noexcept
{
    std::swap(a, other.a);
    std::swap(name, other.name);
}

// 拷贝赋值运算符(值传递参数,利用拷贝构造创建临时对象)
A& operator=(A other)
{
    swap(other); // 交换后原资源被临时对象带走,临时对象销毁时自动释放
    return *this;
}

或采用传统写法:

A& operator=(const A& other)
{
    if (this == &other) return *this; // 处理自赋值

    delete[] a; // 释放当前资源
    // 拷贝新资源
    if (other.a == nullptr) {
        a = nullptr;
    } else {
        a = new int[2]{other.a[0], other.a[1]};
    }
    name = other.name;
    return *this;
}

3. 支持复杂运算赋值

修复后,类即可安全支持x = (x - y) * z + y这类操作——只要你的Vector3和Matrix4运算函数返回合法的对象实例,赋值运算符就能正确处理动态内存的拷贝与释放。

额外建议

  • 优先使用std::unique_ptr<int[]>等智能指针替代裸指针,自动管理内存,避免手动delete的错误。
  • 遵循C的「零法则」:若类不需要自定义析构、拷贝构造、拷贝赋值,则不手动实现;若需要其中一个,通常三个都需要(C11后还需考虑移动构造和移动赋值,即「五法则」)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 12:27:12