С++: нужен ли виртуальный деструктор для объектов, выделенных в стеке?

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

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

Drew Dormann 15.02.2023 23:55

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

kaba 16.02.2023 00:00

Кроме того, если у вас есть метод virtual, вы уже заплатили за vtable, и в случае неполиморфного уничтожения, подобного этому, нет динамической отправки, поэтому добавить его «на всякий случай» кто-то в будущем будет delete через указатель базового класса. по сути бесплатно.

Matteo Italia 16.02.2023 00:06

@kaba - конечно, вы знаете, как будут использоваться ваши классы. Это называется дизайн и документация. Если в вашем классе нет виртуального деструктора, в его документации должно быть сказано об этом; таким образом пользователи будут знать, что он не подходит для использования в качестве базового класса для объектов производных типов, размещенных в свободном хранилище. Программирование — это понимание того, что вы делаете, а не сбор вещей вместе и надежда на то, что результат будет работать более или менее правильно.

Pete Becker 16.02.2023 00:11

Деструктор virtual нужен только для того, чтобы избежать неопределенного поведения при использовании выражения delete. Однако вместо того, чтобы просто не иметь деструктора virtual в вашем случае (и документировать), подумайте, может ли какое-либо будущее использование вашего класса (вашим или кем-то еще) динамически выделять/освобождать объект (new и delete) или вы взяли шаги, чтобы предотвратить это - желательно во время компиляции. Программисты часто недостаточно внимательно читают документацию, чтобы улавливать такие вещи, если только их компилятор не кричит.

Peter 16.02.2023 00:28
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
5
94
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

protected:
  ~Shape2D() = default;

Если вы удалите деструктор, вы вообще не сможете создать объект.

Mark Ransom 16.02.2023 00:58

@PeteFordham — абстрактный класс — это класс с одной или несколькими чистыми виртуальными функциями. Полная остановка. Если нужно построить, то надо построить. Например, он может иметь элемент данных типа std::string.

Pete Becker 16.02.2023 02:11
Ответ принят как подходящий

Поскольку экземпляр производного объекта rct находится в стеке, а не выделяется динамически с помощью new, нет необходимости в виртуальном деструкторе в базе. Это относится к вашему точному коду, а не к общему правилу.

Назначение виртуального деструктора — убедиться, что деструктор производного класса вызывается при удалении производного объекта через указатель базового класса. Это важно, когда деструктор производного класса должен выполнять дополнительную работу, такую ​​как освобождение ресурсов, например. закрытие файлов, освобождение памяти и т.д.

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

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

В стандарте C++ (ISO/IEC 14882:2017) удаление объекта с помощью указателя на базовый класс с невиртуальным деструктором является неопределенным поведением. В частности, в разделе 5.3.5 «Удалить» говорится:

«Если статический тип удаляемого объекта отличается от его динамического типа, статический тип должен быть базовым классом динамического типа удаляемого объекта, а статический тип должен иметь виртуальный деструктор или поведение не определено. ."

Если вы знаете, что ваш код не будет расширен сторонними программами, вы можете рассмотреть альтернативный подход с использованием std::variant или аналогичной конструкции, если она вам доступна.

«Это всегда хорошая практика» ⇒ нет. Это только один из возможных подходов. Вы также можете сделать деструктор невиртуальным protected, если удаление не является частью интерфейса.

spectras 16.02.2023 01:21

Другие вопросы по теме

Похожие вопросы