我对大多数OOP理论都有很好的理解,但最让我困惑的是虚拟析构函数。
我以为析构函数总是被调用,不管是什么,也不管是链中的每个对象。
你打算什么时候让它们虚拟化?为什么?
我对大多数OOP理论都有很好的理解,但最让我困惑的是虚拟析构函数。
我以为析构函数总是被调用,不管是什么,也不管是链中的每个对象。
你打算什么时候让它们虚拟化?为什么?
当前回答
当您可能通过指向基类的指针删除派生类的实例时,虚拟析构函数非常有用:
class Base
{
// some virtual methods
};
class Derived : public Base
{
~Derived()
{
// Do some important cleanup
}
};
在这里,您会注意到我没有将Base的析构函数声明为虚拟。现在,让我们看一下以下片段:
Base *b = new Derived();
// use b
delete b; // Here's the problem!
由于Base的析构函数不是虚拟的,而b是指向派生对象的Base*,因此删除b具有未定义的行为:
[在delete b]中,如果要删除的对象与其动态类型不同,静态类型应为对象的动态类型的基类删除,静态类型应具有虚拟析构函数或行为未定义。
在大多数实现中,对析构函数的调用将像任何非虚拟代码一样被解析,这意味着将调用基类的析构函数,而不是派生类的析构器,从而导致资源泄漏。
总之,当基类的析构函数要以多态方式操作时,请始终将其设为虚拟。
如果要防止通过基类指针删除实例,可以使基类析构函数受保护且非虚拟;通过这样做,编译器不会允许您在基类指针上调用delete。
在本文中,您可以从HerbSutter了解更多关于虚拟性和虚拟基类析构函数的信息。
其他回答
在多态基类中声明虚拟的析构函数。这是Scott Meyers的有效C++中的第7项。Meyers接着总结道,如果一个类有任何虚拟函数,那么它应该有一个虚拟析构函数,而不是设计为基类或不是设计为多态使用的类不应该声明虚拟析构器。
当您希望不同的析构函数在通过基类指针删除对象时遵循正确的顺序时,析构函数的虚拟关键字是必需的。例如:
Base *myObj = new Derived();
// Some code which is using myObj object
myObj->fun();
//Now delete the object
delete myObj ;
如果基类析构函数是虚拟的,那么对象将按顺序被析构函数(首先是派生对象,然后是基)。如果基类析构函数不是虚拟的,那么只有基类对象会被删除(因为指针是基类“base*myObj”)。因此派生对象将存在内存泄漏。
通过指向基类的指针调用析构函数
struct Base {
virtual void f() {}
virtual ~Base() {}
};
struct Derived : Base {
void f() override {}
~Derived() override {}
};
Base* base = new Derived;
base->f(); // calls Derived::f
base->~Base(); // calls Derived::~Derived
虚拟析构函数调用与任何其他虚拟函数调用都没有区别。
对于base->f(),调用将被分派到Derived::f()中,对于base->~base()也是如此-它的重写函数-将调用Derived::~Derived()。
间接调用析构函数时也会发生同样的情况,例如delete base;。delete语句将调用base->~base(),该函数将被分派到Derived::~Derived()。
具有非虚拟析构函数的抽象类
若您不打算通过指向其基类的指针删除对象,那个么就不需要使用虚拟析构函数。只需保护它,使其不会被意外调用:
// library.hpp
struct Base {
virtual void f() = 0;
protected:
~Base() = default;
};
void CallsF(Base& base);
// CallsF is not going to own "base" (i.e. call "delete &base;").
// It will only call Base::f() so it doesn't need to access Base::~Base.
//-------------------
// application.cpp
struct Derived : Base {
void f() override { ... }
};
int main() {
Derived derived;
CallsF(derived);
// No need for virtual destructor here as well.
}
只要类是多态的,就将析构函数设为虚拟。
虚拟构造函数是不可能的,但虚拟析构函数是可能的。让我们做个实验。。。。。。。
#include <iostream>
using namespace std;
class Base
{
public:
Base(){
cout << "Base Constructor Called\n";
}
~Base(){
cout << "Base Destructor called\n";
}
};
class Derived1: public Base
{
public:
Derived1(){
cout << "Derived constructor called\n";
}
~Derived1(){
cout << "Derived destructor called\n";
}
};
int main()
{
Base *b = new Derived1();
delete b;
}
上述代码输出以下内容:
Base Constructor Called
Derived constructor called
Base Destructor called
派生对象的构造遵循构造规则,但当我们删除“b”指针(基指针)时,我们发现只有基析构函数被调用。但这绝不能发生。为了做适当的事情,我们必须使基析构函数虚拟化。现在让我们看看以下情况:
#include <iostream>
using namespace std;
class Base
{
public:
Base(){
cout << "Base Constructor Called\n";
}
virtual ~Base(){
cout << "Base Destructor called\n";
}
};
class Derived1: public Base
{
public:
Derived1(){
cout << "Derived constructor called\n";
}
~Derived1(){
cout << "Derived destructor called\n";
}
};
int main()
{
Base *b = new Derived1();
delete b;
}
输出变化如下:
Base Constructor Called
Derived Constructor called
Derived destructor called
Base destructor called
因此,基指针的销毁(对派生对象进行分配!)遵循销毁规则,即首先是派生指针,然后是基指针。另一方面,没有什么像虚拟构造函数。