基类指针指向派生类对象时的正确解构与删除方法问询
当派生类指针赋值给基类指针后,如何正确析构派生类对象?同时能否以正确方式删除该派生类对象?以下是具体场景:
#include <stdio.h> #include <iostream> #include <map> #include <stddef.h> class Signal{ public: Signal (int val) : x(val){}; int x; virtual ~Signal(){ std::cout<<"base class destructor"<<std::endl; } }; class Timer : public Signal{ public: Timer (int val) : Signal(val), y(val+1){}; double y; virtual ~Timer(){ std::cout<<"derived class destructor"<<std::endl; }; }; int main() { std::cout<<"start"<<std::endl; Timer* t = new Timer(9); Signal* s = t; delete s; std::cout<<t->x <<std::endl; std::cout<<t->y <<std::endl; s = NULL; if (t != NULL){ delete t; } std::cout<<"end"<<std::endl; return 0; }
代码执行输出:
start
derived class destructor
base class destructor
-340049904
10...Program finished with exit code 0
Press ENTER to exit console.
已知避免Signal* s = t;赋值时程序可正常运行,但当前程序未打印预期的“end”,执行delete t时停止,无论是否添加t != NULL检查均是如此。
问题分析与解决方案
核心问题
代码触发了未定义行为:delete s已经释放了t指向的Timer对象内存,后续delete t属于对已释放内存的二次删除,这会导致程序崩溃或异常终止,因此无法执行到打印"end"的代码。另外,delete s后访问t->x和t->y属于访问野指针,结果是随机的(比如x的垃圾值)。
正确的处理方式
仅删除一次对象
由于s和t指向同一个堆对象,只需要调用一次delete即可。因为你已经将基类的析构函数声明为virtual,通过基类指针delete s会自动先调用派生类Timer的析构函数,再调用基类Signal的析构函数,能完整清理派生类对象的所有资源。修正后的
main函数示例:int main() { std::cout<<"start"<<std::endl; Timer* t = new Timer(9); Signal* s = t; // 一次delete即可完成完整析构 delete s; // 或者使用 delete t; 效果完全相同 // 禁止访问已释放内存的指针 s = nullptr; t = nullptr; std::cout<<"end"<<std::endl; return 0; }杜绝野指针操作
对象被释放后,应立即将所有指向它的指针置为nullptr(C++11及以后推荐),避免后续误操作。保持基类析构为虚函数
这是多态场景下正确析构派生类的必要条件。如果基类析构不是虚函数,通过基类指针delete只会调用基类析构函数,派生类的成员资源不会被清理,造成内存泄漏。
为什么原代码没打印"end"
二次delete触发了未定义行为,程序在执行delete t时直接终止,因此后续的std::cout<<"end"<<std::endl;无法执行。
内容的提问来源于stack exchange,提问作者Jankovsky222

