Безопасно ли в частном порядке наследовать класс с не виртуальным деструктором?
Я понимаю, что с публичным наследованием это вообще не безопасно, с каких пор delete
ing указатель базового класса компилятор генерирует только код для вызова деструктора базового класса, а производный класс не вызывается.
но для частного наследования клиент не может привести указатель производного класса к указателю базового класса (поскольку частное наследование не моделирует is-a отношения), так delete
всегда используется для указателя производного класса, и компилятор должен иметь возможность видеть, что существует также базовый класс и вызывать его деструктор.
Я сделал этот тест:
#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';
}
оба GCC 4.7.1 и CLang 3.1 дают одинаковый выход. Конструктор производного класса вызывается, за исключением случаев, когда сам производный класс приводит указатель производного класса к базовому классу и delete
С его.
кроме этого случая, который кажется довольно необычным и легко избежать (автор класса-единственный парень, который может причинить вред, но он делает знаете, из какого класса он получил свой), могу ли я заключить, что он безопасен?
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
бонус-вопрос: как насчет защищенного наследования? Я полагаю, что способность причинять вред больше не является прерогативой автора непосредственно производного класса, А авторов любого класса в иерархии.
2 ответов
является ли наследование публичным или частным, не влияет на безопасность кода, оно просто ограничивает область, в которой его можно использовать безопасно/небезопасно. У вас есть та же основная проблема: если ваш класс или друг вашего класса передает объект вашего типа интерфейсу, который принимает указатель на базу без виртуального деструктора, и если этот интерфейс получает право собственности на ваш объект, вы создаете неопределенное поведение.
проблема в дизайне заключается в том, что согласно вашему вопрос BaseNonVirtual
не предназначен для расширения. Если это так, он должен иметь либо общедоступный виртуальный деструктор, либо защищенный не виртуальный, гарантируя, что ни один код не сможет вызвать delete на производном объекте через указатель на базу.
есть случай, когда клиентский код can cast производный к базе, несмотря на частное наследование:
delete reinterpret_cast<BaseNonVirtual*>(new DerivedPrivNonVirtual);
таким образом пропуская исполнение ~DerivedPrivNonVirtual()
.
но, учитывая, сколько использование reinterpret_cast
обескуражен, вы можете заключить, что это" достаточно безопасно " для ваших целей.