C++中派生类赋值给基类变量时析构函数被调用两次的问题
派生类赋值给基类变量导致double free的原因与解决方案
问题原因
你遇到的double free问题,核心是对象切片和默认浅拷贝共同作用的结果,具体执行流程如下:
Base b = Derived();执行时,先创建临时Derived对象:依次调用Base构造(分配a的内存)、Derived构造(分配b的内存)。- 用临时
Derived对象拷贝构造基类对象b:由于对象切片,派生类独有的b指针被丢弃,仅复制基类部分的a指针;而基类的默认拷贝构造是浅拷贝,直接复制指针值,导致b.a和临时对象的a指向同一块内存。 - 临时
Derived对象销毁:先调用Derived析构(释放b),再调用Base析构(释放a)。 - 局部对象
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
相关产品推荐
相关产品推荐

