Я понимаю, что с общим наследованием он вообще небезопасен, поскольку при delete
указании на базовый класс компилятор генерирует только код для вызова деструктора базового класса, а производный класс один не вызывается.
Но для частного наследования клиент не может наложить указатель на производный класс на указатель базового класса (поскольку частное наследование не является моделью-отношением), поэтому delete
всегда используется для указателя производного класса, а компилятор должен уметь видеть, что есть также базовый класс и вызвать его деструктор.
Я сделал этот тест:
#include <iostream>
struct BaseVirtual
{
virtual ~BaseVirtual()
{
std::cout << "BaseVirtual dtor" << '\n';
}
};
struct BaseNonVirtual
{
~BaseNonVirtual()
{
std::cout << "BaseNonVirtual dtor" << '\n';
}
};
struct DerivedPrivVirtual: private BaseVirtual
{
static void f()
{
BaseVirtual * p = new DerivedPrivVirtual;
delete p;
}
~DerivedPrivVirtual()
{
std::cout << "DerivedPrivVirtual dtor" << '\n';
}
};
struct DerivedPrivNonVirtual: private BaseNonVirtual
{
static void f()
{
BaseNonVirtual * p = new DerivedPrivNonVirtual;
delete p;
}
~DerivedPrivNonVirtual()
{
std::cout << "DerivedPrivNonVirtual 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';
}
Оба GCC 4.7.1 и CLang 3.1 дают одинаковый результат. Вызывается конструктор производного класса, за исключением случаев, когда сам производный класс отличает указатель производного класса к базовому классу и delete
it.
Помимо этого случая, который кажется довольно необычным и легко можно избежать (автор класса - единственный парень, который может нанести вред, но он знает, из какого класса он получил его), могу ли я сделать вывод, что он безопасен?
With explicit derived pointer type:
DerivedPrivVirtual dtor
BaseVirtual dtor
DerivedPrivNonVirtual dtor
BaseNonVirtual dtor
With base pointer type:
Inside derived class itself:
DerivedPrivVirtual dtor
BaseVirtual dtor
BaseNonVirtual dtor <-- Only a problem inside the class itself
With non-dynamic variables:
DerivedPrivNonVirtual dtor
BaseNonVirtual dtor
DerivedPrivVirtual dtor
BaseVirtual dtor
Бонусный вопрос: как насчет защищенного наследования? Я полагаю, что способность причинять вред больше не является прерогативой непосредственно созданного автора класса, а авторам любого класса в иерархии.