I understand that with public inheritance it is in general not safe, since when delete
ing 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 delete
s 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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With