В чем преимущество наличия «защищенного не виртуального деструктора» по сравнению с «защищенным виртуальным деструктором»? - PullRequest
1 голос
/ 09 ноября 2019

Читая от Херба Саттера, я вижу следующее:

Рекомендация № 4: Деструктор базового класса должен быть либо общедоступным и> виртуальным, либо защищенным и не виртуальным.

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

вот мой код:

#include <iostream>
using namespace std;

class base {
public:
    base () {cout << "base ctor" << endl;}
    virtual void foo () = 0;
protected:
    ~base() {cout << "base dtor" << endl;}
};
class derived : public base {
public:
        derived () {cout << "derived ctor" << endl;}
        virtual void foo () override {cout << "derived foo" << endl;}
        virtual ~derived(){cout << "derived dtor" << endl;}
};

int main(){
    derived* b = new derived();
        delete b;
    cout <<"done"<<endl;
}

Что я получу, сделав базовый dtor не виртуальным против виртуальным ? Я вижу тот же эффект, будь то виртуальный или не виртуальный.

base ctor
derived ctor
derived dtor
base dtor
done

1 Ответ

2 голосов
/ 10 ноября 2019

Я вижу тот же эффект, будь то виртуальный или не виртуальный.

Это потому, что вы вызываете delete через указатель на производный тип. Если вместо этого вы сделаете деструктор общедоступным, но не виртуальным, и выполните

base* b = new derived();
delete b;

, он (скорее всего) напечатает

base ctor
derived ctor
base dtor
done

(я не могу гарантировать, что он будетprint, потому что поведение не определено при удалении объекта производного типа через указатель на базовый класс, если деструктор не является виртуальным)

В этом случае, когда компилятор может видеть как вызов new, так ина delete это, скорее всего, скажет вам, что у вас здесь неопределенное поведение, но если одна единица перевода просто передает вам указатель на базу, а вы вызываете delete в другой, то нет никакого способа узнать это для компилятора. Если вы хотите быть уверенным, что не можете совершить эту ошибку, есть два способа избежать этой проблемы.

Первый - просто сделать виртуальный деструктор;тогда не будет неопределенного поведения. Но, конечно, это имеет небольшое постоянство производительности, поскольку уничтожение теперь имеет один дополнительный уровень косвенности через vtable.

Так что, если вы никогда не намереваетесь хранить объекты в (smart-) указателях на базовый класс и использовать толькополиморфизм через ссылки, то вы можете не захотеть платить эти дополнительные расходы.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...