我对大多数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了解更多关于虚拟性和虚拟基类析构函数的信息。

其他回答

只要类是多态的,就将析构函数设为虚拟。

当您可能通过指向基类的指针删除派生类的实例时,虚拟析构函数非常有用:

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接着总结道,如果一个类有任何虚拟函数,那么它应该有一个虚拟析构函数,而不是设计为基类或不是设计为多态使用的类不应该声明虚拟析构器。

虚拟构造函数是不可能的,但虚拟析构函数是可能的。让我们做个实验。。。。。。。

#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

因此,基指针的销毁(对派生对象进行分配!)遵循销毁规则,即首先是派生指针,然后是基指针。另一方面,没有什么像虚拟构造函数。