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

C++中派生类赋值给基类变量时析构函数被调用两次的问题

派生类赋值给基类变量导致double free的原因与解决方案

问题原因

你遇到的double free问题,核心是对象切片和默认浅拷贝共同作用的结果,具体执行流程如下:

  1. Base b = Derived(); 执行时,先创建临时Derived对象:依次调用Base构造(分配a的内存)、Derived构造(分配b的内存)。
  2. 用临时Derived对象拷贝构造基类对象b:由于对象切片,派生类独有的b指针被丢弃,仅复制基类部分的a指针;而基类的默认拷贝构造是浅拷贝,直接复制指针值,导致b.a和临时对象的a指向同一块内存。
  3. 临时Derived对象销毁:先调用Derived析构(释放b),再调用Base析构(释放a)。
  4. 局部对象b销毁:再次调用Base析构,尝试释放已经被临时对象销毁的a内存,最终触发double free错误。

即使基类析构设为virtual也无法解决,因为这里不存在多态行为——b是基类的栈对象,不是指针/引用,销毁时只会调用基类析构,核心问题是浅拷贝导致的指针重复释放。

正确处理方式

针对不同场景,有以下几种解决方案:

1. 禁止基类的拷贝操作(推荐用于多态类)

如果类体系是为多态设计,不需要基类对象被拷贝,可以显式删除拷贝构造和赋值运算符,从根源阻止切片赋值:

class Base{
private:
    int* a;
public:
    Base() {
        std::cout << "Base constructor" << std::endl;
        a = new int{6};
    }

    virtual ~Base(){
        std::cout << "Base destructor" << std::endl;
        delete a;
    }

    // 禁止拷贝构造和赋值
    Base(const Base&) = delete;
    Base& operator=(const Base&) = delete;
};

此时编译器会直接报错,阻止Base b = Derived();这类危险操作。

2. 使用智能指针管理内存

将裸指针替换为std::unique_ptr或std::shared_ptr,智能指针会自动处理内存所有权转移,避免浅拷贝导致的重复释放:

#include <iostream>
#include <memory>

class Base{
private:
    std::unique_ptr<int> a;
public:
    Base() : a(std::make_unique<int>(6)) {
        std::cout << "Base constructor" << std::endl;
    }

    virtual ~Base(){
        std::cout << "Base destructor" << std::endl;
    }
};

class Derived: public Base {
private:
    std::unique_ptr<int> b;
public:
    Derived() : b(std::make_unique<int>(7)) {
        std::cout << "Derived constructor" << std::endl;
    }

    ~Derived(){
        std::cout << "Derived destructor" << std::endl;
    }
};

注:std::unique_ptr禁止拷贝,Base b = Derived();会编译报错,反而避免了切片问题;如果需要共享所有权,可以用std::shared_ptr。

3. 使用指针/引用实现多态(正确的多态用法)

如果需要保留多态行为,应该使用基类指针或引用,而非值语义:

// 使用智能指针自动管理内存(推荐)
int main() {
    std::unique_ptr<Base> b = std::make_unique<Derived>();
    return 0;
}

// 或者手动管理指针(需注意delete)
int main() {
    Base* b = new Derived();
    delete b; // 基类析构是virtual,会先调用Derived析构再调用Base析构
    return 0;
}

这种方式不会发生对象切片,且能正确调用派生类的析构函数,避免内存泄漏和double free。

4. 实现深拷贝构造与赋值运算符

如果确实需要基类对象可以拷贝,必须实现深拷贝,确保每个对象拥有独立的内存空间:

class Base{
private:
    int* a;
public:
    Base() {
        std::cout << "Base constructor" << std::endl;
        a = new int{6};
    }

    // 深拷贝构造
    Base(const Base& other) {
        std::cout << "Base copy constructor" << std::endl;
        a = new int{*other.a}; // 复制值而非指针
    }

    // 深拷贝赋值运算符
    Base& operator=(const Base& other) {
        std::cout << "Base assignment operator" << std::endl;
        if (this != &other) { // 防止自赋值
            delete a;
            a = new int{*other.a};
        }
        return *this;
    }

    virtual ~Base(){
        std::cout << "Base destructor" << std::endl;
        delete a;
    }
};

此时Base b = Derived();执行时,拷贝构造会为b.a分配新内存,临时对象和b的a指向不同内存,销毁时不会重复释放。注意:派生类也需要实现对应的深拷贝逻辑,避免其内部资源丢失或泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 13:01:18