Форум программистов, компьютерный форум, киберфорум
Наши страницы
С++ для начинающих
Войти
Регистрация
Восстановить пароль
 
Andrey040601
4 / 4 / 3
Регистрация: 13.07.2014
Сообщений: 127
Завершенные тесты: 5
#1

Виртуальный деструктор, для чего нужен? - C++

27.01.2015, 21:48. Просмотров 1499. Ответов 10
Метки нет (Все метки)

Я конечно понимаю, что
Если деструктор объявлен как виртуальный, то при удалении объекта производного класса, на который ссылается указатель базового класса, будет вызван деструктор соответствующего производного класса. Затем деструктор производного класса автоматически вызовет деструктор базового класса.
Но объясните поподробнее и попонятнее для чего это нужно
1
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
27.01.2015, 21:48
Я подобрал для вас темы с готовыми решениями и ответами на вопрос Виртуальный деструктор, для чего нужен? (C++):

Почему создается виртуальный деструктор A, а в таблице виртуальных функций лежит деструктор B
Почему я делаю виртуальным деструктор A, а в таблице виртуальных функций лежит...

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

Виртуальный деструктор
Всем привет! Объясните пожалуйста новичку в ООП, вопрос чисто теоретический...

Виртуальный деструктор на MinGW v4.9.2
не знаю как на прошлых версиях, но на этой не работает чисто виртуальный...

Виртуальный деструктор и уничтожение объектов
Приветствую всех. вопрос наверное простой так, что не ругайтесь. столкнулся...

Почему виртуальный деструктор вызывается дважды?
Непонятно: #include <iostream> #include <conio.h> #include<string> ...

10
Jewbacabra
Эксперт PHP
3093 / 2680 / 1226
Регистрация: 24.04.2014
Сообщений: 8,198
27.01.2015, 22:39 #2
Цитата Сообщение от Andrey040601 Посмотреть сообщение
Но объясните поподробнее и попонятнее для чего это нужно
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
using namespace std;
 
class Base {
public:
    Base() {
        a = new int;
    }
    ~Base() {
        cout << "Deleting a\n";
        delete a;
    }
private:
    int * a;
};
 
class Derived : public Base {
public:
    Derived() {
        b = new int;
    }
    ~Derived() {
        cout << "Deleting b\n";
        delete b;
    }
private:
    int * b;
};
 
int main() {
    Base * d = new Derived; // Без виртуального деструктора Base b не будет удалено
    delete d;
    return 0;
}
1
Tulosba
:)
Эксперт С++
4746 / 3240 / 496
Регистрация: 19.02.2013
Сообщений: 9,046
27.01.2015, 22:46 #3
Допустим, есть иерархия типов:
C++
1
2
struct B {};
struct D : B {};
Если деструкторы будут не виртуальными, то следующий код приводит к неопределенному поведению (UB):
C++
1
2
B* obj = new D;
delete obj;
Будет вызван деструктор, связанный статически (т.е. на этапе компиляции) с типом obj, т.е. деструктор класса B.
Чтобы код отработал корректно и был вызван как деструктор производного класса, так и деструктор базового, нужно сделать деструктор в базовом классе виртуальным:
C++
1
2
3
struct B {
   virtual ~B() {}
};
При такой реализации базового класса код с delete будет работать правильно.
2
Убежденный
Ушел с форума
Эксперт С++
15941 / 7252 / 1176
Регистрация: 02.05.2013
Сообщений: 11,637
Записей в блоге: 1
Завершенные тесты: 1
27.01.2015, 22:48 #4
Когда в C++ ты работаешь с объектом через указатель или ссылку на
базовый класс, от которого тот унаследован, то при вызове метода базового
класса вызывается его перегруженная версия из класса-наследника (если
она есть). Это называется полиморфизм. Простой пример:
C++
1
2
serializer * pSerializer = getSerializer();
pSerializer->saveData(Data);
Здесь serializer вполне может быть абстрактным классом, в котором объявлен
виртуальный метод saveData. А наследники, - file_serializer, reg_serializer,
http_serializer и т.п., - реализуют этот метод каждый по-своему:
reg_serializer пишет данные в реестр, http_serializer отправляет их по
сети и т.д. Конкретный класс выбирается во время выполнения программы.
В данном случае это может происходить в теле getSerializer:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct serializer
{
    virtual void saveData(data const & Data) = 0;
    virtual ~serializer() {} // virtual destructor.
};
 
struct file_serializer : public serializer { /* ... */ };
struct reg_serializer  : public serializer { /* ... */ };
struct http_serializer : public serializer { /* ... */ };
 
serializer * getSerializer()
{
    if      (/* condition-1 */) return (new file_serializer());
    else if (/* condition-2 */) return (new reg_serializer());
    else if (/* condition-3 */) return (new http_serializer());
 
    return NULL; // error.
}
Для клиента такая "маскировка" очень удобна: он работает с конкретной
реализацией serializer-а через обобщенный интерфейс, не беспокоясь о деталях.

И вот когда приходит время удалить объект через указатель на базу,
возникают некоторые нюансы:
C++
1
2
3
4
5
6
7
8
// 1.
serializer * pSerializer = getSerializer();
 
// 2.
pSerializer->saveData(Data);
 
// 3.
delete pSerializer;
Происходящее в точке 3 напрямую зависит от того, есть ли в базовом классе
serializer виртуальный деструктор. Если есть - для всех объектов, которые
"скрыты" полиморфизмом за указателем на базу, будут вызваны соответствующие
деструкторы и удаление произойдет корректно. Если нет - поведение не
определено (то есть, может произойти все, что угодно, от простой утечки
памяти до вылетов программы и других глюков).

По этой причине в C++ существует жесткое (негласное) правило:
если при создании класса есть хотя бы одна вероятность на миллион, что у
него будут наследники и они будут удаляться полиморфно (т.е. через
указатель или ссылку на базу), определи в классе виртуальный деструктор.

Еще один пример:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>
 
struct animal
{
    virtual void who_i_am() = 0;
    virtual ~animal() {} // virtual destructor.
};
 
struct cat : public animal
{
    void who_i_am()
    {
        printf("I am a cat.\r\n");
    }
    
    ~cat()
    {
        printf("Cat: bye !\r\n");
    }
};
 
struct dog : public animal
{
    void who_i_am()
    {
        printf("I am a dog.\r\n");
    }
    
    ~dog()
    {
        printf("Dog: bye !\r\n");
    }
};
 
int main()
{
    animal * pAnimal1 = new cat();
    animal * pAnimal2 = new dog();
    
    pAnimal1->who_i_am();
    pAnimal2->who_i_am();
    
    delete pAnimal2;
    delete pAnimal1;
    
    return 0;
}
Вывод:
I am a cat.
I am a dog.
Dog: bye !
Cat: bye !
А вот что будет без виртуального деструктора в animal
(внимание! может быть все, что угодно):
I am a cat.
I am a dog.
Как видим, деструкторы производных классов не были вызваны.

Напоследок добавлю, что идея без разбора делать все деструкторы виртуальными,
порочна. Как только класс получает хотя бы одну виртуальную функцию,
размер одного экземпляра такого класса увеличивается по меньшей мере на
"sizeof (void *)" байт (поведение не стандартизировано, но по факту так).
Так что всегда следует искать золотую середину.

Не по теме:


Jewbacabra, Tulosba, простой ведь вопрос, а как все ринулись помогать

1
hoggy
Заблокирован
27.01.2015, 22:49 #5
Цитата Сообщение от Tulosba Посмотреть сообщение
Если деструкторы будут не виртуальными, то следующий код приводит к неопределенному поведению (UB):
C++
1
2
B* obj = new D;
delete obj;
Вы ошибаетесь. Нет никакого UB, для нефиртульного диструктора будет вызван диструктор B
1
Tulosba
:)
Эксперт С++
4746 / 3240 / 496
Регистрация: 19.02.2013
Сообщений: 9,046
27.01.2015, 22:55 #6
Цитата Сообщение от hoggy Посмотреть сообщение
Вы ошибаетесь.
Читай Стандарт перед тем как писать такие фразы. 5.3.5/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.
2
hoggy
Заблокирован
27.01.2015, 23:11 #7
Цитата Сообщение от Tulosba Посмотреть сообщение
Читай Стандарт перед тем как писать такие фразы. 5.3.5/3
Хотя трактовка более чем странная.

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

С другой стороны, официальное признание ситуации UB означает,
что нет никаких "вполне понятных причин".

Словно поведение виртуального диструктора чем то принципиально отличается от поведения любой другой виртуальной функции.

Тем не менее, немножко понимая, как устроен полиморфизм с точки зрения низкоуровневой реализации,
я больше чем уверен, что принципиальных отличий нет.

Скорее всего стандарт с легкой руки записал в UB обычную ill-formed.
1
Убежденный
Ушел с форума
Эксперт С++
15941 / 7252 / 1176
Регистрация: 02.05.2013
Сообщений: 11,637
Записей в блоге: 1
Завершенные тесты: 1
27.01.2015, 23:24 #8
Цитата Сообщение от hoggy Посмотреть сообщение
Скорее всего стандарт с легкой руки записал в UB обычную ill-formed.
Любопытно, как бы Вы прокомментировали такой код:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct foo
{
    virtual void func1() {}
};
     
struct bar
{
    virtual void func2() {}
};
     
struct foobar : public foo, public bar
{
};
     
int main()
{
    bar * p = new foobar();
    delete p; // Удаление через "второй" базовый класс.
    return 0;
}
На всех доступных мне компиляторах результат одинаковый - crash.
2
hoggy
Заблокирован
28.01.2015, 00:38 #9
Цитата Сообщение от Убежденный Посмотреть сообщение
Любопытно, как бы Вы прокомментировали такой код:
При создании объекта:
C++
1
bar * p = new foobar();
Значение указателя p по факту не будет совпадать со значением this объекта foobar.
На самом деле оно будет с некоторым смещением относительно this объекта foobar.

Это связанно с тем, что при множественном наследовании, компилятор объединяет в объекте-наследнике его личные данные, и все базовые подобъекты с некоторым смещением:

[ vtabl foobar ] <--- начало наследника
[ data foobar ]
[ vtabl foo ] <--- подобъект foo
[ data foo ]
[ vtabl bar ] <--- подобъект bar
[ data bar ]
Насколько я знаю, принцип размещения подобъектов не стандартизирован.
И может быть различным у разных компиляторов.

Ну да не суть.

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

Или когда мы делаем каст от наследника к предку - он тоже автоматом учитывает смещение.

Поэтому, можно совершенно свободно приводить наследника к базовому классу, и работать с ним, как с объектом базового класса.

Собственно, в этом случае так и получается: объект по факту наследник, но работа ведется с его "базовой частью".

(надеюсь из контекста понятно, что я специально не затрагиваю виртуальные функции)

Соответственно, при удалении с не виртуальным диструктором все абсолютно тоже самое:

Так как он не виртуальный, то работа буквально ведется с "базовой частью" объекта.

Фактически мы удаляем не весь объект, а только его отдельную базовую часть.

При этом благополучно запустится диструктор базового класса.
Благополучно отработает.

А крэш происходит уже после разрушения срезанного объекта.

Мы выделяли один кусок, а удалить попытались грубо говоря что-то начиная с серединки.

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

Она фиксирует явно некорректное поведения: нельзя частично освободить запрошенную память. Только всю целиком. Иначе это трактуется, как сбойное поведение.

И ось посылает сбойному приложению сигнал.
1
Andrey040601
4 / 4 / 3
Регистрация: 13.07.2014
Сообщений: 127
Завершенные тесты: 5
28.01.2015, 14:57  [ТС] #10
Цитата Сообщение от Jewbacabra Посмотреть сообщение
C++
1
Base * d = new Derived;
А в чем смысл такой записи?
1
aLarman
644 / 565 / 164
Регистрация: 13.12.2012
Сообщений: 2,112
Завершенные тесты: 1
28.01.2015, 16:41 #11
Andrey040601, тут читаем
1
28.01.2015, 16:41
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
28.01.2015, 16:41
Привет! Вот еще темы с решениями:

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

Вопрос новичка про виртуальный деструктор
Если инициализировать указатель родительского класса адресом объекта дочернего...

Подскажите как правильно добавить виртуальный деструктор
Доброго времени суток Подскажите пожалуйста, как правильно добавить...

Как правильно сохранить структуру, унаследованную от структуры, содержащую виртуальный деструктор?
Здравствуйте! Имеется структура: struct Product { int mId; double...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
11
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru