Подтвердить что ты не робот

Безопасно ли частное наследование от класса с не виртуальным деструктором?

Я понимаю, что с общим наследованием он вообще небезопасен, поскольку при 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

Бонусный вопрос: как насчет защищенного наследования? Я полагаю, что способность причинять вред больше не является прерогативой непосредственно созданного автора класса, а авторам любого класса в иерархии.

4b9b3361

Ответ 1

Независимо от того, является ли наследование общедоступным или закрытым, это не влияет на безопасность кода, оно просто ограничивает область, в которой оно может использоваться безопасно/небезопасно. У вас есть одна и та же основная проблема: если ваш класс или друг вашего класса передает объект вашего типа интерфейсу, который принимает указатель на базу без виртуального деструктора, и если этот интерфейс приобретает право собственности на ваш объект, вы создаете undefined.

Проблема в дизайне заключается в том, что по вашему вопросу BaseNonVirtual не предназначен для расширения. Если это так, у него должен быть либо виртуальный виртуальный деструктор, либо защищенный не виртуальный, гарантирующий, что ни один код не сможет вызвать delete на производном объекте с помощью указателя на базу.

Ответ 2

Существует случай, когда клиентский код может отличать Derived до Base, несмотря на личное наследование:

delete reinterpret_cast<BaseNonVirtual*>(new DerivedPrivNonVirtual);

Таким образом, пропустите выполнение ~DerivedPrivNonVirtual().

Но, учитывая, насколько не рекомендуется использовать reinterpret_cast, вы можете сделать вывод, что он "достаточно безопасен" для ваших целей.