Я новичок в C++ и хотел бы знать, требуется ли использование виртуального деструктора в следующей программе:
#include <iostream>
class Shape2D {
protected:
int firstDimension;
int secondDimension;
public:
Shape2D(int a, int b) {
firstDimension = a;
secondDimension = b;
}
virtual void getArea() {
std::cout << "Area undefined." << std::endl;
}
};
class Rectangle : public Shape2D {
public:
Rectangle(int length=0, int width=0) : Shape2D(length, width) {
}
void getArea() {
int area = firstDimension*secondDimension;
std::cout << "Area of rectangle: " << area << " sq units." << std::endl;
}
};
int main()
{
Rectangle rct(4, 5); // stack allocated derived object
Shape2D *sh = &rct; // base pointer to derived object
sh->getArea();
// object goes out of scope here.
}
Я читал этот пост, и в ответе, получившем наибольшее количество голосов, говорилось, что использование виртуального деструктора подходит всякий раз, когда мы хотим удалить производный объект, выделенный new
через его базовый указатель. Однако в этом контексте мы размещаем производный объект Rectangle
в стеке в подпрограмме main
, а базовый указатель не подвергается delete
-ed, поэтому имеет ли значение наличие виртуального деструктора?
Вы, вероятно, правы в своих рассуждениях: в вашем примере компилятор знает тип вашего производного объекта и вызовет соответствующий деструктор. «Но» здесь заключается в том, что в реальном коде вы никогда не знаете, как будут использоваться ваши классы. Поэтому в реальном коде вы всегда должны предоставлять виртуальные деструкторы в базовых классах.
Кроме того, если у вас есть метод virtual
, вы уже заплатили за vtable, и в случае неполиморфного уничтожения, подобного этому, нет динамической отправки, поэтому добавить его «на всякий случай» кто-то в будущем будет delete
через указатель базового класса. по сути бесплатно.
@kaba - конечно, вы знаете, как будут использоваться ваши классы. Это называется дизайн и документация. Если в вашем классе нет виртуального деструктора, в его документации должно быть сказано об этом; таким образом пользователи будут знать, что он не подходит для использования в качестве базового класса для объектов производных типов, размещенных в свободном хранилище. Программирование — это понимание того, что вы делаете, а не сбор вещей вместе и надежда на то, что результат будет работать более или менее правильно.
Деструктор virtual
нужен только для того, чтобы избежать неопределенного поведения при использовании выражения delete
. Однако вместо того, чтобы просто не иметь деструктора virtual
в вашем случае (и документировать), подумайте, может ли какое-либо будущее использование вашего класса (вашим или кем-то еще) динамически выделять/освобождать объект (new
и delete
) или вы взяли шаги, чтобы предотвратить это - желательно во время компиляции. Программисты часто недостаточно внимательно читают документацию, чтобы улавливать такие вещи, если только их компилятор не кричит.
Виртуальный деструктор не требуется, как и общедоступный деструктор. В этом случае рекомендуется удалить общедоступный деструктор (сделав его защищенным).
protected:
~Shape2D() = default;
Если вы удалите деструктор, вы вообще не сможете создать объект.
@PeteFordham — абстрактный класс — это класс с одной или несколькими чистыми виртуальными функциями. Полная остановка. Если нужно построить, то надо построить. Например, он может иметь элемент данных типа std::string
.
Поскольку экземпляр производного объекта rct находится в стеке, а не выделяется динамически с помощью new, нет необходимости в виртуальном деструкторе в базе. Это относится к вашему точному коду, а не к общему правилу.
Назначение виртуального деструктора — убедиться, что деструктор производного класса вызывается при удалении производного объекта через указатель базового класса. Это важно, когда деструктор производного класса должен выполнять дополнительную работу, такую как освобождение ресурсов, например. закрытие файлов, освобождение памяти и т.д.
Обычно считается хорошей практикой предоставлять виртуальный деструктор в базовом классе, если он предназначен для использования в качестве полиморфного базового класса, а производные объекты должны выделяться динамически. Существуют и другие варианты, такие как пометка вашего невиртуального деструктора как частного или защищенного, что приведет к ошибкам компиляции при попытке удалить через базу.
Если вы намеревались удалить через базовый класс, необходимо иметь виртуальный деструктор, чтобы избежать неопределенного поведения.
В стандарте C++ (ISO/IEC 14882:2017) удаление объекта с помощью указателя на базовый класс с невиртуальным деструктором является неопределенным поведением. В частности, в разделе 5.3.5 «Удалить» говорится:
«Если статический тип удаляемого объекта отличается от его динамического типа, статический тип должен быть базовым классом динамического типа удаляемого объекта, а статический тип должен иметь виртуальный деструктор или поведение не определено. ."
Если вы знаете, что ваш код не будет расширен сторонними программами, вы можете рассмотреть альтернативный подход с использованием std::variant или аналогичной конструкции, если она вам доступна.
«Это всегда хорошая практика» ⇒ нет. Это только один из возможных подходов. Вы также можете сделать деструктор невиртуальным protected
, если удаление не является частью интерфейса.
Ответ, на который вы ссылаетесь, правильный. В вашем коде вообще нет
delete
. Когда вашRectangle
уничтожается, компилятор знает, что этоRectangle
. Вам не нужен виртуальный деструктор.