Destruidor não virtual na classe base, mas destruidor virtual na classe derivada causa falha de segmentação

Recentemente, em uma entrevista de emprego, fui questionado sobre o problema de vazamento de memória em classes derivadas quando o destruidor da classe base não é declarado virtual.

Eu escrevi um pequeno teste para confirmar minha resposta, mas encontrei algo interessante. Obviamente, se você criar umDerived objeto vianew mas guarde seu ponteiro como umBase*, o destrutor do objeto derivado não será chamado, se o ponteiro for excluído (tanto para minha resposta à pergunta).

Eu pensei se o destrutor da classe derivada é virtual ou não é irrelevante nesse caso, mas no meu sistema o seguinte código mostra o contrário:

#include <iostream>
#include <string>

// just a helper class, printing its name out when it is destructed
class PrintOnDestruct
{
    public:
        PrintOnDestruct( const std::string& name )
        : name_( name )
        {}

        ~PrintOnDestruct()
        {
            std::cout << "Destructing: " << name_ << std::endl;
        }

    protected:

        std::string name_;
};

// the Base class
class Base
{
    public:
        Base()
        {
            print_on_destruct_ = new PrintOnDestruct( "Base" );
        }

        // the destructor is NOT virtual!
        ~Base()
        {
            delete print_on_destruct_;
        }

    protected:

        PrintOnDestruct* print_on_destruct_;

};

// the NonVirtualDerived class, doesn't have a virtual destructor either
class NonVirtualDerived : public Base
{
    public:
        NonVirtualDerived()
        : Base()
        {
            print_on_destruct_child_ = new PrintOnDestruct( "NonVirtualDerived" );
        }

        // the destructor is NOT virtual!
        ~NonVirtualDerived()
        {
            delete print_on_destruct_child_;
        }

    protected:

        PrintOnDestruct* print_on_destruct_child_;

};

// the VirtualDerived class does have a virtual destructor 
class VirtualDerived : public Base
{
    public:
        VirtualDerived()
        : Base()
        {
            print_on_destruct_child_ = new PrintOnDestruct( "VirtualDerived" );
        }

        // the destructor is virtual!
        virtual ~VirtualDerived()
        {
            delete print_on_destruct_child_;
        }

    protected:

        PrintOnDestruct* print_on_destruct_child_;

};

int main()
{
    // create the two child classes
    Base* non_virtual_derived = new NonVirtualDerived;
    Base* virtual_derived = new VirtualDerived;

    // delete the two objects
    delete non_virtual_derived; // works as expected (only calls Base's destructor, the memory of NonVirtualDerived will be leaked)
    delete virtual_derived; // segfault, after calling Base's destructor

    return 0;
}

Eu esperava que o programa gerasse as duas linhas seguintes e saia normalmente:

Destructing: Base
Destructing: Base

Eu recebo essa saída, mas imediatamente após a segunda linha o programa sai com uma falha de segmentação. E a mensagem:

*** Error in `...': free(): invalid pointer: 0x00000000006020e8 ***

Eu mudei a ordem das duas chamadas paradelete, mas o programa sempre se separaria na chamada paradelete virtual_derived;. Alguém pode me dizer por que isso é assim?

questionAnswers(2)

yourAnswerToTheQuestion