Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to privately inherit from a class with a non-virtual destructor?

I understand that with public inheritance it is in general not safe, since when deleteing a base class pointer the compiler only generates code to call base class's destructor, and the derived class's one is not called.

But for private inheritance the client can not cast a derived class pointer to a base class pointer (as private inheritance does not model is-a relationship), so delete is always used on derived class's pointer, and the compiler should be able to see that there is also a base class and to call its destructor.

I made this test:

#include <iostream>

struct BaseVirtual
{
    virtual ~BaseVirtual()
    {
        std::cout << "BaseVirtual's dtor" << '\n';
    }
};

struct BaseNonVirtual
{
    ~BaseNonVirtual()
    {
        std::cout << "BaseNonVirtual's dtor" << '\n';
    }
};

struct DerivedPrivVirtual: private BaseVirtual
{
    static void f()
    {
        BaseVirtual * p = new DerivedPrivVirtual;
        delete p;
    }

    ~DerivedPrivVirtual()
    {
        std::cout << "DerivedPrivVirtual's dtor" << '\n';
    }
};

struct DerivedPrivNonVirtual: private BaseNonVirtual
{
    static void f()
    {
        BaseNonVirtual * p = new DerivedPrivNonVirtual;
        delete p;
    }

    ~DerivedPrivNonVirtual()
    {
        std::cout << "DerivedPrivNonVirtual's dtor" << '\n';
    }
};

int main()
{
    std::cout << "With explicit derived pointer type:" << '\n';
    {
        DerivedPrivVirtual * derivedPrivVirtual = new DerivedPrivVirtual;
        DerivedPrivNonVirtual * derivedPrivNonVirtual = new DerivedPrivNonVirtual;

        delete derivedPrivVirtual;
        delete derivedPrivNonVirtual;
    }
    std::cout << '\n';

    std::cout << "With base pointer type:" << '\n';
    {
        // Client code can't cast Derived to Base when inherit privately.
        //BaseVirtual * derivedPrivVirtual = new DerivedPrivVirtual;
        //BaseNonVirtual * derivedPrivNonVirtual = new DerivedPrivNonVirtual;

        //delete derivedPrivVirtual;
        //delete derivedPrivNonVirtual;
    }
    std::cout << '\n';

    std::cout << "Inside derived class itself:" << '\n';
    {
        DerivedPrivVirtual::f();
        DerivedPrivNonVirtual::f();
    }
    std::cout << '\n';

    std::cout << "With non-dynamic variables:" << '\n';
    {
        DerivedPrivVirtual derivedPrivVirtual;
        DerivedPrivNonVirtual derivedPrivNonVirtual;
    }
    std::cout << '\n';
}

Both GCC 4.7.1 and CLang 3.1 give the same output. The derived class constructor is called except when the derived class itself casts a derived class pointer to base class and deletes it.

Besides this case which seems quite uncommon and easily avoidable (class's author is the only guy who can do harm, but it does know from which class it derived its one), can I conclude that it is safe?

With explicit derived pointer type:
DerivedPrivVirtual's dtor
BaseVirtual's dtor
DerivedPrivNonVirtual's dtor
BaseNonVirtual's dtor

With base pointer type:

Inside derived class itself:
DerivedPrivVirtual's dtor
BaseVirtual's dtor
BaseNonVirtual's dtor  <-- Only a problem inside the class itself

With non-dynamic variables:
DerivedPrivNonVirtual's dtor
BaseNonVirtual's dtor
DerivedPrivVirtual's dtor
BaseVirtual's dtor

Bonus question: what about protected inheritance? I suppose that the ability to do harm is no longer prerogative to directly derived class's author, but to authors of any class in the hierarchy.

like image 666
Claudio Avatar asked Sep 17 '12 17:09

Claudio


2 Answers

Whether inheritance is public or private does not affect the safety of the code, it just limits the scope in which it can be used safely/unsafely. You have the same basic issue: if your class or a friend of your class passes an object of your type to an interface that takes a pointer to the base without virtual destructor, and if that interface acquires ownership of your object then you are creating undefined behavior.

The problem in the design is that as per your question, the BaseNonVirtual is not designed to be extended. If it was, it should have either a public virtual destructor, or a protected non-virtual one, ensuring that no code will be able to call delete on a derived object through a pointer to the base.

like image 135
David Rodríguez - dribeas Avatar answered Nov 18 '22 15:11

David Rodríguez - dribeas


There is a case where client code can cast Derived to Base despite private inheritance:

delete reinterpret_cast<BaseNonVirtual*>(new DerivedPrivNonVirtual);

Thus skipping execution of ~DerivedPrivNonVirtual().

But, given how much the use of reinterpret_cast is discouraged, you may conclude that it's "safe enough" for your purposes.

like image 38
feuGene Avatar answered Nov 18 '22 17:11

feuGene