Notice: file_put_contents(): Write of 5625 bytes failed with errno=28 No space left on device in /var/www/tgoop/post.php on line 50

Warning: file_put_contents(): Only 16384 of 22009 bytes written, possibly out of free disk space in /var/www/tgoop/post.php on line 50
C++95@cxx95 P.67
CXX95 Telegram 67
#creepy

Миф о виртуальных деструкторах 🍅

На собеседованиях и в реальной жизни часто встречается вопрос: "Зачем нужен виртуальный деструктор?"

В очень достоверном источнике знаний (то есть в интернете) практически везде написано в таком ключе:
Если у вас в классе присутствует хотя бы один виртуальный метод, деструктор также следует сделать виртуальным. При этом не следует забывать, что деструктор по умолчанию виртуальным не будет, поэтому следует объявить его явно. Если этого не сделать, у вас в программе почти наверняка будут UB (скорее всего в виде утечек памяти).

Но есть логичный вопрос: почему бы тогда компилятору не генерировать виртуальный деструктор автоматически (для классов с виртуальными методами)? 🤔
Ведь он кучу всего генерирует сам (например move- и copy-конструкторы, move- и copy-операторы присваивания).

Ответ: Потому что утверждение выше неправильное! 😱 Виртуальный деструктор нужен только тогда, когда есть риск, что объект класса-наследника могут разрушить по указателю на класс-предок.

Пусть есть базовый виртуальный класс DrinkMachine и его наследник класс CoffeeMachine.
Каждый объект программы рано или поздно надо разрушить, это делается в 2х разных случаях:
1️⃣ Объект создан на стеке, тогда программа сама вызовет деструктор
{
CoffeeMachine cm;
// перед выходом из scope сам вызывается cm.~CoffeeMachine()
}
В этом случае неважно какой деструктор (виртуальный или нет), потому что в обоих случаях вызовется то что нужно - деструктор реального объекта.

2️⃣ Объект создан в куче, тогда программист сам делает разрушение объекта, обычно так:
DrinkMachine* dm = ...; // возможно вместо `...` здесь был `new CoffeeMachine()`
// ...
delete dm;
(или если у нас объект по типу std::unique_ptr<DrinkMachine> - происходит то же самое)

Оператор delete обычно делает две вещи: вызывает деструктор и освобождает память:
dm->~DrinkMachine();
std::free(dm);

В этом случае важно чтобы вызвался именно деструктор реального объекта, т.е. возможно мы на самом деле хотели бы вызвать ~CoffeeMachine(). В этом случае нужен виртуальный деструктор - он будет лежать во vtable, и как метод будет находиться динамически.

Если второго случая в программе не бывает, то виртуальный деструктор не нужен - например в этой программе все работает без ошибок:
void MakeDrink(DrinkMachine& dm) {
dm.MakeDrink(); // вызов виртуального метода
}
void MakeCoffee() {
CoffeeMachine cm;
MakeDrink(cm);
// cm удалится сам через `cm.~CoffeeMachine()`
}

Это может быть важно для программ, которых нужно оптимизировать, потому что вызов виртуального метода (в т.ч. виртуального деструктора) дает оверхед в виде двух memory load.

P. S. Есть такой прикол как девиртуализация - когда компилятор сразу понимает какой метод нужно вызвать (не глядя во vtable). Но для этого компилятор должен доказать, что он точно знает, какой метод нужно вызвать. Хорошая статья на эту тему.

Девиртуализация не стандартизирована, поэтому нужно проверять на своем компиляторе самому - оптимизируется ли вызов виртуального деструктора в примере с MakeCoffee выше или нет. Если да - то можно забить и всегда делать виртуальный деструктор.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥3🖕1



tgoop.com/cxx95/67
Create:
Last Update:

#creepy

Миф о виртуальных деструкторах 🍅

На собеседованиях и в реальной жизни часто встречается вопрос: "Зачем нужен виртуальный деструктор?"

В очень достоверном источнике знаний (то есть в интернете) практически везде написано в таком ключе:

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

Но есть логичный вопрос: почему бы тогда компилятору не генерировать виртуальный деструктор автоматически (для классов с виртуальными методами)? 🤔
Ведь он кучу всего генерирует сам (например move- и copy-конструкторы, move- и copy-операторы присваивания).

Ответ: Потому что утверждение выше неправильное! 😱 Виртуальный деструктор нужен только тогда, когда есть риск, что объект класса-наследника могут разрушить по указателю на класс-предок.

Пусть есть базовый виртуальный класс DrinkMachine и его наследник класс CoffeeMachine.
Каждый объект программы рано или поздно надо разрушить, это делается в 2х разных случаях:
1️⃣ Объект создан на стеке, тогда программа сама вызовет деструктор
{
CoffeeMachine cm;
// перед выходом из scope сам вызывается cm.~CoffeeMachine()
}
В этом случае неважно какой деструктор (виртуальный или нет), потому что в обоих случаях вызовется то что нужно - деструктор реального объекта.

2️⃣ Объект создан в куче, тогда программист сам делает разрушение объекта, обычно так:
DrinkMachine* dm = ...; // возможно вместо `...` здесь был `new CoffeeMachine()`
// ...
delete dm;
(или если у нас объект по типу std::unique_ptr<DrinkMachine> - происходит то же самое)

Оператор delete обычно делает две вещи: вызывает деструктор и освобождает память:
dm->~DrinkMachine();
std::free(dm);

В этом случае важно чтобы вызвался именно деструктор реального объекта, т.е. возможно мы на самом деле хотели бы вызвать ~CoffeeMachine(). В этом случае нужен виртуальный деструктор - он будет лежать во vtable, и как метод будет находиться динамически.

Если второго случая в программе не бывает, то виртуальный деструктор не нужен - например в этой программе все работает без ошибок:
void MakeDrink(DrinkMachine& dm) {
dm.MakeDrink(); // вызов виртуального метода
}
void MakeCoffee() {
CoffeeMachine cm;
MakeDrink(cm);
// cm удалится сам через `cm.~CoffeeMachine()`
}

Это может быть важно для программ, которых нужно оптимизировать, потому что вызов виртуального метода (в т.ч. виртуального деструктора) дает оверхед в виде двух memory load.

P. S. Есть такой прикол как девиртуализация - когда компилятор сразу понимает какой метод нужно вызвать (не глядя во vtable). Но для этого компилятор должен доказать, что он точно знает, какой метод нужно вызвать. Хорошая статья на эту тему.

Девиртуализация не стандартизирована, поэтому нужно проверять на своем компиляторе самому - оптимизируется ли вызов виртуального деструктора в примере с MakeCoffee выше или нет. Если да - то можно забить и всегда делать виртуальный деструктор.

BY C++95


Share with your friend now:
tgoop.com/cxx95/67

View MORE
Open in Telegram


Telegram News

Date: |

The SUCK Channel on Telegram, with a message saying some content has been removed by the police. Photo: Telegram screenshot. Telegram has announced a number of measures aiming to tackle the spread of disinformation through its platform in Brazil. These features are part of an agreement between the platform and the country's authorities ahead of the elections in October. Telegram Channels requirements & features End-to-end encryption is an important feature in messaging, as it's the first step in protecting users from surveillance. 6How to manage your Telegram channel?
from us


Telegram C++95
FROM American