我对大多数OOP理论都有很好的理解,但最让我困惑的是虚拟析构函数。

我以为析构函数总是被调用,不管是什么,也不管是链中的每个对象。

你打算什么时候让它们虚拟化?为什么?


当前回答

我认为这个问题的核心是关于虚拟方法和多态性,而不是具体的析构函数。下面是一个更清晰的例子:

class A
{
public:
    A() {}
    virtual void foo()
    {
        cout << "This is A." << endl;
    }
};

class B : public A
{
public:
    B() {}
    void foo()
    {
        cout << "This is B." << endl;
    }
};

int main(int argc, char* argv[])
{
    A *a = new B();
    a->foo();
    if(a != NULL)
    delete a;
    return 0;
}

将打印出:

This is B.

如果没有虚拟,它将打印出:

This is A.

现在您应该了解何时使用虚拟析构函数。

其他回答

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

还要注意,在没有虚拟析构函数时删除基类指针将导致未定义的行为。我最近学到的东西:

C++中重写删除应该如何操作?

我已经使用C++多年了,但我还是设法自杀了。

我认为讨论“未定义”行为,或者至少讨论在通过没有虚拟析构函数的基类(/struct)删除时可能发生的“崩溃”未定义行为,或者更准确地说,没有vtable,是有益的。下面的代码列出了一些简单的结构(类也是如此)。

#include <iostream>
using namespace std;

struct a
{
    ~a() {}

    unsigned long long i;
};

struct b : a
{
    ~b() {}

    unsigned long long j;
};

struct c : b
{
    ~c() {}

    virtual void m3() {}

    unsigned long long k;
};

struct d : c
{
    ~d() {}

    virtual void m4() {}

    unsigned long long l;
};

int main()
{
    cout << "sizeof(a): " << sizeof(a) << endl;
    cout << "sizeof(b): " << sizeof(b) << endl;
    cout << "sizeof(c): " << sizeof(c) << endl;
    cout << "sizeof(d): " << sizeof(d) << endl;

    // No issue.

    a* a1 = new a();
    cout << "a1: " << a1 << endl;
    delete a1;

    // No issue.

    b* b1 = new b();
    cout << "b1: " << b1 << endl;
    cout << "(a*) b1: " << (a*) b1 << endl;
    delete b1;

    // No issue.

    c* c1 = new c();
    cout << "c1: " << c1 << endl;
    cout << "(b*) c1: " << (b*) c1 << endl;
    cout << "(a*) c1: " << (a*) c1 << endl;
    delete c1;

    // No issue.

    d* d1 = new d();
    cout << "d1: " << d1 << endl;
    cout << "(c*) d1: " << (c*) d1 << endl;
    cout << "(b*) d1: " << (b*) d1 << endl;
    cout << "(a*) d1: " << (a*) d1 << endl;
    delete d1;

    // Doesn't crash, but may not produce the results you want.

    c1 = (c*) new d();
    delete c1;

    // Crashes due to passing an invalid address to the method which
    // frees the memory.

    d1 = new d();
    b1 = (b*) d1;
    cout << "d1: " << d1 << endl;
    cout << "b1: " << b1 << endl;
    delete b1;  

/*

    // This is similar to what's happening above in the "crash" case.

    char* buf = new char[32];
    cout << "buf: " << (void*) buf << endl;
    buf += 8;
    cout << "buf after adding 8: " << (void*) buf << endl;
    delete buf;
*/
}

我并不是建议你是否需要虚拟析构函数,尽管我认为一般来说,拥有它们是一个很好的做法。我只是指出了如果基类(/struct)没有vtable,而派生类(/struck)有vtable,并且通过基类(/ststruct)指针删除对象,那么可能会导致崩溃的原因。在这种情况下,传递给堆的空闲例程的地址是无效的,因此是崩溃的原因。

如果运行上述代码,您将清楚地看到问题发生的时间。当基类(/struct)的this指针与派生类(/struct)的this指示器不同时,您将遇到此问题。在上面的示例中,结构a和b没有vtables。结构c和d确实有vtables。因此,指向c或d对象实例的a或b指针将被修复以说明vtable。如果传递此a或b指针进行删除,则会由于地址对堆的空闲例程无效而崩溃。

如果计划从基类指针中删除具有vtable的派生实例,则需要确保基类具有vtable。一种方法是添加一个虚拟析构函数,您可能无论如何都希望它能正确地清理资源。

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

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

关于virtual的一个基本定义是它确定类的成员函数是否可以在其派生类中被覆盖。

类的D-tor基本上在作用域的末尾被调用,但存在一个问题,例如,当我们在堆(动态分配)上定义一个实例时,我们应该手动删除它。

一旦指令被执行,就会调用基类析构函数,但不会调用派生的析构函数。

一个实际的例子是,在控制场中,你必须操纵效应器和致动器。

在范围结束时,如果没有调用其中一个动力元件(执行器)的析构函数,将产生致命的后果。

#include <iostream>

class Mother{

public:

    Mother(){

          std::cout<<"Mother Ctor"<<std::endl;
    }

    virtual~Mother(){

        std::cout<<"Mother D-tor"<<std::endl;
    }


};

class Child: public Mother{

    public:

    Child(){

        std::cout<<"Child C-tor"<<std::endl;
    }

    ~Child(){

         std::cout<<"Child D-tor"<<std::endl;
    }
};

int main()
{

    Mother *c = new Child();
    delete c;

    return 0;
}