Страницы

Поиск по вопросам

понедельник, 30 декабря 2019 г.

Можно ли игнорировать пустого наследника при написании деструктора?

#cpp #наследование #language_lawyer #неопределенное_поведение


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

Меня же интересует случай, когда дочерний класс не добавляет к родительскому ничего,
что следовало бы уничтожать. Например, вообще не добавляет никаких полей к базовому
классу. Можно ли в таком случае проигнорировать правило виртуальности деструктора или
даже в таком случае удаление будет UB?

Пример кода: https://ideone.com/B3Wx7m

struct A {
  int *p;
  A(unsigned n) : p(new int[n]) {}
  ~A() { delete [] p; }
};

struct B : A
{
  B() : A(4) {}
};

int main()
{
  A *a = new B();
  delete a;

  return 0;
}

    


Ответы

Ответ 1



В стандарте четко сказано, что такое удаление вызывает неопределенное поведение. Не делается никаких оговорок на тему того, добавлены ли какие-то поля в класс-наследник или нет. 5.3.5 Delete 3 In the first alternative (delete object), if the static type of the object to be deleted is different from its dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined. [...] На деструктор класса также обычно накладываются (могут накладываться) какие-то неявные служебные функции, которые могут работать неправильно при вызове неправильного деструктора. В частности, в типичной реализации на деструктор возложена обязанность вызова правильной функции operator delete, которая может быть перегружена для каждого класса отдельно. То есть если бы в struct B была определена своя статическая функция operator delete, то без виртуального деструктора обеспечивать ее функциональность пришлось бы какими-то другими методами. (Авторы стандарта, скорее всего, и ориентировались именно на такую реализацию вызова перегруженного operator delete.) Например #include struct A { // virtual ~A() {} }; struct B : A { static void *operator new(size_t s) { return (char *) std::malloc(s * 2) + s; } static void operator delete(void *p, size_t s) { std::free((char *) p - s); } }; int main() { A* p = new B; delete p; } В популярных реализациях такой код упадет из-за вызова неправильного operator delete. Но достаточно добавить в A виртуальный деструктор, как все станет хорошо. Обратите внимание, что оба члена B - статические функции, то есть физически в B ничего не добавлено.

Комментариев нет:

Отправить комментарий