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

Переопределение по умолчанию виртуального деструктора

Всем известно, что дескриптор базового класса обычно должен быть виртуальным. Но что такое деструктор производного класса? В С++ 11 мы имеем ключевое слово "переопределить" и возможность явно использовать деструктор по умолчанию.

struct Parent
{
  std::string a;
  virtual ~Parent()
  {
  }

};

struct Child: public Parent
{
  std::string b;
  ~Child() override = default;
};

Правильно ли использовать оба слова "переопределить" и "= по умолчанию" в деструкторе класса Child? Будет ли компилятор генерировать правильный виртуальный деструктор в этом случае?

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

4b9b3361

Ответ 1

Правильно ли использовать оба слова "переопределить" и "= по умолчанию" в деструкторе класса Child? Будет ли компилятор генерировать правильный виртуальный деструктор в этом случае?

Да, это правильно. В любом разумном компиляторе, если код компилируется без ошибок, это определение деструктора будет no-op: его отсутствие не должно изменять поведение кода.

можем ли мы думать, что это хороший стиль кодирования

Это вопрос предпочтения. Для меня это имеет смысл только в том случае, если шаблон базового класса является шаблоном: тогда он будет применять требование к базовому классу, чтобы иметь виртуальный деструктор. В противном случае, когда базовый тип исправлен, я считаю, что такой код является помехой. Это не так, как если бы базовый класс волшебным образом изменился. Но, если у вас есть умные товарищи по команде, которые любят менять вещи, не проверяя код, который зависит от того, что они могут сломать, лучше оставить определение деструктора в качестве дополнительного уровня защиты.

Ответ 2

override - не что иное, как защитная сетка. Деструктор дочернего класса всегда будет виртуальным, если деструктор базового класса является виртуальным, независимо от того, как он объявлен или вообще не объявлен (т.е. С использованием неявно объявленного).

Ответ 3

Существует (по крайней мере) одна причина для использования override здесь - вы гарантируете, что деструктор базового класса всегда является виртуальным. Это будет ошибка компиляции, если деструктор производного класса полагает, что он что-то переопределяет, но переопределить нечего. Это также дает вам удобное место, чтобы оставить сгенерированную документацию, если вы это делаете.

С другой стороны, я могу думать о двух причинах, чтобы не делать этого:

  • Это немного странно и назад для производного класса для обеспечения поведения из базового класса.
  • Если вы определяете destuctor в заголовке (или, если вы делаете его inline), вы вводите возможность для ошибок нечетной компиляции. Скажем, ваш класс выглядит следующим образом:

    struct derived {
        struct impl;
        std::unique_ptr<derived::impl> m_impl;
        ~derived() override = default;
    };
    

    Вероятно, вы получите ошибку компилятора, потому что деструктор (который встроен в класс здесь) будет искать деструктор для неполного класса derived::impl.

    Это мой круглый способ сказать, что каждая строка кода может стать ответственностью, и, возможно, лучше всего просто пропустить что-то, если оно функционально ничего не делает. Если вам действительно нужно принудительно ввести виртуальный деструктор в базовый класс из родительского класса, кто-то предложил использовать static_assert совместно с std::has_virtual_destructor, что даст более согласованные результаты, IMHO.

Ответ 4

Хотя деструкторы не наследуются, в стандарте четко написано, что виртуальные деструкторы производных классов переопределяют деструкторы базовых классов.

Из стандарта С++ (10.3 виртуальных функций)

6 Несмотря на то, что деструкторы не наследуются, деструктор в производном class переопределяет деструктор базового класса, объявленный виртуальным; см. 12.4 и 12.5.

С другой стороны, также написано (9.2 члена класса)

8. Вирд-спецификатор должен содержать не более одного из каждого вир-специфика. Вирд-спецификатор должен появляться только в объявлении a виртуальная функция-член (10.3).

Хотя деструкторы называются как специальные функции-члены, тем не менее они также являются функциями-членами.

Я уверен, что С++ Standard должен быть отредактирован таким образом, чтобы было однозначно, может ли деструктор иметь virt-specifier override. В настоящее время неясно.

Ответ 5

Я думаю, что "переопределить" является неправильным для деструктора. Когда вы переопределяете виртуальную функцию, вы ее заменяете. Деструкторы цепочки, поэтому вы не можете буквально перекрыть деструктор

Ответ 6

Ссылка CPP говорит, что override гарантирует, что функция virtual и что она действительно отменяет виртуальную функцию. Таким образом, ключевое слово override гарантирует, что деструктор виртуальный.

Если вы укажете override, но не = default, вы получите ошибку компоновщика.

Вам ничего не нужно делать. Выход из Child dtor undefined работает просто отлично:

#include <iostream>

struct Notify {
    ~Notify() { std::cout << "dtor" << std::endl; }
};

struct Parent {
    std::string a;
    virtual ~Parent() {}
};

struct Child : public Parent {
    std::string b;
    Notify n;
};

int main(int argc, char **argv) {
    Parent *p = new Child();
    delete p;
}

Будет выводиться dtor. Если вы удалите virtual в Parent::~Parent, он ничего не выведет, потому что это поведение undefined, как указано в комментариях.

Хороший стиль - вообще не упоминать Child::~Child. Если вы не можете доверять базовому классу, объявившему его виртуальным, то ваше предложение с override и = default будет работать; Я надеюсь, что есть лучшие способы гарантировать, что вместо того, чтобы засорять ваш код этими объявлениями деструктора.

Ответ 7

Хорошо для меня ключевое слово override после деструктора производного класса не имеет никакого смысла, потому что всегда происходит то, что сначала вызывается деструктор производного класса, а затем вызывается деструктор родительского класса. Переопределите заметки о том, что функция отличается от ее партер-виртуальной функции, которая в этом случае неверна, потому что они оба вызываются.

Так что я бы сказал, это то, что все зависит от предпочтения.

P.S.: Я считаю, что деструктор default никогда не бывает плохим. Он сообщает компилятору, что вы его оставляете, чтобы он мог сделать некоторые внутренние оптимизации. Это также относится к деструктору производного класса.