C++杂记——虚函数之构造函数和析构函数
@
本文将说明为什么构造函数不可以为虚函数以及析构函数什么场景下需要是虚函数
构造函数
在C++中,基类的构造函数不能定义为虚函数,主要有以下几个原因:
- 构造过程的顺序:构造函数的调用顺序是从基类到派生类的。如果基类的构造函数是虚的,那么在创建派生类对象时,动态绑定的机制(根据对象的实际类型来决定调用哪个函数)会导致不确定性,因为基类构造函数在派生类构造函数之前被调用。
- 没有多态性:在构造函数的上下文中,虚表(vtable)尚未建立。当基类构造函数执行时,派生类的部分尚未被构造,因此无法实现虚函数的多态性。换句话说,基类的构造函数不会参照派生类的重载版本。
- 潜在的未定义行为:如果允许基类的构造函数是虚函数,可能会导致在构造过程中调用未定义的行为或访问未初始化的资源,增加了程序的不稳定性。
- 设计的意图:构造函数的主要目的是初始化对象的状态。虚函数的目的在于实现运行时多态。将这两者结合在一起会分散关注点,违反类的封装和设计原则。
因此,为了保持类的构造过程的清晰与一致性,C++规定基类的构造函数不能是虚函数。这有助于确保创建对象的稳定性和安全性。
析构函数
多态是面向对象的一个基本属性,包括静态多态(编译阶段)和动态多态(运行阶段),静态多态主要是指函数参数不同产生的多态性,是在编译阶段可以识别的一种多态机制,而运行时多态则主要用于基类指针指向派生类对象时,可以通过基类指针直接调用派生类的对象函数,当然这种多态是通过虚函数实现的。
虚函数的目的就是通知系统在函数调用时能够自动识别对应的类对象类型,从而能够根据指针所指类型调用对应的类对象,实现函数调用时的多态性。对于析构函数而言,同样适用于上述规则。 如果析构函数不是虚函数,那么在调用该函数时(对象被删除时)则只会调用当前对象对应的类的析构函数,这对于直接定义的对象是没有什么影响的,但是对于使用基类指向派生类的指针而言,因为基类指针实际上是基类类型,所以析构时自然只会调用基类的析构函数,这就可能产生内存泄漏(因为派生类的析构函数不被调用)。所以如果确定程序中有基类指针指向派生类的问题,则必须将基类的析构函数指定为虚函数,如此才能确保NEW出来的对象被正确的DELETE。
每一个derived class destructor 会被编译器加以扩展,以静态调用的方式调用其“每一个virtual base class”以及“上一层base class”的destructor.因此,只要缺乏任何一个base class destructors的定义,就会导致连接失败。
显式调用析构函数
- 显式调用的时候,析构函数相当于的一个普通的成员函数
- 编译器隐式调用析构函数,如分配了对内存,显式调用析构的话引起重复释放堆内存的异常
- 把一个对象看作占用了部分栈内存,占用了部分堆内存(如果申请了的话),这样便于理解这个问题
- 系统隐式调用析构函数的时候,会加入释放栈内存的动作(而堆内存则由用户手工的释放)
- 用户显式调用析构函数的时候,只是单纯执行析构函数内的语句,不会释放栈内存,摧毁对象
异常与构造函数和析构函数
在 C++ 中,构造函数和析构函数是可以抛出异常的,但通常不推荐这样做。
构造函数抛出异常:
- 如果构造函数在对象创建过程中抛出异常,整个对象的创建会失败,无法使用该对象。
- 在这种情况下,可能需要考虑将构造函数中的初始化逻辑进行安全检查,避免抛出异常。
- 常见的做法是通过返回错误码或使用其他异常安全的策略来处理可能的错误。
析构函数抛出异常:
- 析构函数抛出异常会导致 std::terminate 被调用,这在程序中是非常不希望发生的。
- 如果某个对象的析构过程中发生异常,应优先处理这些异常,比如通过捕获并记录错误,确保在析构过程中不抛出异常。
推荐的做法:
- 设计构造函数和析构函数时,尽量遵循异常安全原则。在构造函数中,可以使用初始化列表,确保资源的正确管理;在析构函数中,使用 RAII(资源获取即初始化)模式来管理资源的释放。
- 在构造函数中可以使用 try-catch 块来捕获异常并做相应处理,而在析构函数中则必须特别小心,避免抛出异常。
总结:
尽管 C++ 允许构造函数和析构函数抛出异常,但实际上在设计时应尽量避免这种做法,以保证代码的健壮性和可维护性。
参考
Why virtual constructor in C++ is not possible? – Reason
What is virtual destructor in C++? Why do we need virtual desturctor?
Why don't we have virtual constructors?
Advanced C++ | Virtual Constructor

浙公网安备 33010602011771号