为什么基类析构函数需要是虚函数
析构函数作用
析构函数是进行类的清理工作,比如释放内存、关闭DB链接、关闭Socket等等,为实现多态性(C++多态),可以通过基类的指针或引用访问派生类的成员。也就是说,声明一个基类指针,这个基类指针可以指向派生类对象。
基类析构函数不是虚函数
#include <iostream> class Base { public: // 注意,这里的析构函数没有定义为虚函数 ~Base() { std::cout << "Base destructor called." << std::endl; } }; class Derived : public Base { public: Derived() { resource = new int[100]; // 分配资源 } ~Derived() { std::cout << "Derived destructor called." << std::endl; delete[] resource; // 释放资源 } private: int* resource; // 存储资源的指针 }; int main() { Base* ptr = new Derived(); delete ptr; // 只会调用Base的析构函数,Derived的析构函数不会被调用 return 0; } /* 运行结果: Base destructor called. */
由于基类Base
的析构函数没有定义为虚函数,当创建一个派生类Derived
的对象,并通过基类指针ptr
删除它时,只有基类Base
的析构函数被调用(因为这里没有多态,构造多态的必要条件就是虚函数)。派生类Derived
的析构函数不会被调用,导致指针指向的内存(resource
)没有被释放,从而产生内存泄漏。
基类析构函数设置为虚函数
class Base { public: virtual ~Base() { std::cout << "Base destructor called." << std::endl; } }; class Derived : public Base { public: ~Derived() { std::cout << "Derived destructor called." << std::endl; } }; int main() { Base *ptr = new Derived(); delete ptr; // 调用Derived的析构函数,然后调用Base的析构函数 return 0; } /* 运行结果: Derived destructor called. Base destructor called. */
在这个例子中,基类Base
的析构函数是虚函数,所以当删除ptr
时,会首先调用派生类Derived
的析构函数,然后调用基类Base
的析构函数,这样可以确保对象被正确销毁。
为什么类的默认析构函数不设置为虚函数
虚函数不同于普通成员函数,当类中有虚成员函数时,类会自动进行一些额外工作。这些额外的工作包括生成虚函数表和虚表指针,虚表指针指向虚函数表。每个类都有自己的虚函数表,虚函数表的作用就是保存本类中虚函数的地址,我们可以把虚函数表形象地看成一个数组,这个数组的每个元素存放的就是各个虚函数的地址。这样一来,就会占用额外的内存,当们定义的类不被其他类继承时,这种内存开销无疑是浪费的。因此当我们创建一个类时,系统默认我们不会将该类作为基类,所以就将默认的析构函数定义成非虚函数,这样就不会占用额外的内存空间。同时,系统也相信程序开发者在定义一个基类时,会显示地将基类的析构函数定义成虚函数,此时该类才会维护虚函数表和虚表指针。
零成本抽象原则
这也就是 C++ 的一个设计哲学:zero overhead abstraction
我的理解,所谓「零成本抽象」有两个层面的意思。
- 不需要为没有使用到的语言特性付出代价。
- 使用某种语言特性,不会带来运行时的代价。
放在这个地方就是,如果我们知道一个类不会被其它类继承,那么也就没必要将析构函数设置为虚函数,因为一旦引入虚函数就会引入虚表机制,这会造成额外的开销。
总结
在C++中,基类的析构函数需要定义为虚函数,以确保在通过基类指针或引用删除派生类对象时,能够正确地调用派生类的析构函数,否则派生类的析构函数不会被调用,这部分资源也就并无法被释放。
将基类的析构函数定义为虚函数后,C++运行时会自动识别指向的对象的实际类型,并确保调用正确的派生类析构函数。当派生类析构函数执行完毕后,基类的析构函数也会自动调用,以确保对象完全销毁。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!