зачем нужны виртуальные деструкторы

Виртуальные функции и деструктор

Когда-то давным давно я собирался и даже обещал написать про механизм виртуальных функций относительно деструкторов. Теперь у меня наконец появилось свободное время и я решил воплотить эту затею в жизнь. На самом деле эта мини-статья служит «прологом» к моей следующей статье. Но я постарался изложить доходчиво и понятно основные моменты по текущей теме. Если вы чувствуете, что еще недостаточно разобрались в механизме виртуальных вызовов, то, возможно, вам следует для начала прочитать мою предыдущую статью.

Сразу же, как обычно, оговорюсь, что: 1) статья моя не претендует на полноту изложения материала; 2) мегапрограммеры ничего нового здесь не узнают; 3) материал не новый и давно описан во многих книгах, но если явно об этом не прочитать и самому специально не задумываться, то можно о некоторых моментах даже не подозревать (до поры, до времени). Также прошу прощения за надуманные примеры 🙂

Виртуальные деструкторы

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

Основное правило: если у вас в классе присутствует хотя бы одна виртуальная функция, деструктор также следует сделать виртуальным. При этом не следует забывать, что деструктор по умолчанию виртуальным не будует, поэтому следует объявить его явно. Если этого не сделать, у вас в программе почти наверняка будут утечки памяти (memory leaks). Чтобы понять почему, опять же много ума не надо. Рассмотрим несколько примеров.

В первом случае создадим объект производного класса в стеке:

using std :: cout ;
using std :: endl ;

Всем ясно, что вывод программы будет следующим:

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

Попробуем теперь создать тот же объект в динамической памяти, используя при этом указатель на объект базового класса (код классов не изменился, поэтому привожу только код функции main()):

int main ( )
<
A * pA = new B ;
delete pA ;
return EXIT_SUCCESS ;
>

На сей раз конструируется объект так, как и надо, а при разрушении происходит утечка памяти, потому как деструктор производного класса не вызывается:

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

Чтобы этого избежать, деструктор в базовом классе должен быть объявлен как виртуальный:

using std :: cout ;
using std :: endl ;

int main ( )
<
A * pA = new B ;
delete pA ;
return EXIT_SUCCESS ;
>

Теперь-то мы получим желаемый порядок вызовов:

Происходит так потому, что отныне для вызова деструктора используется позднее связывание, то есть при разрушении объекта берется указатель на класс, затем из таблицы виртуальных функций определяется адрес нужного нам деструктора, а это деструктор производного класса, который после своей работы, как и полагается, вызывает деструктор базового. Итог: объект разрушен, память освобождена.

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

Давайте для начала рассмотрим ситуацию с вызовом виртуальных функций внутри класса. Предположим, что у нас есть Кот, который просит покушать мяуканьем, а затем приступает к процессу 🙂 Так поступают многие коты, но не Чеширский! Чеширский, как известно, мало того что вечно улыбается, так еще и довольно разговорчив, поэтому мы научим его говорить, переопределив метод speak():

using std :: cout ;
using std :: endl ;

class Cat
<
public :
void askForFood ( ) const
<
speak ( ) ;
eat ( ) ;
>
virtual void speak ( ) const < cout "Meow! " ; >
virtual void eat ( ) const < cout "*champing*" endl ; >
> ;

class CheshireCat : public Cat
<
public :
virtual void speak ( ) const < cout "WTF?! Where \' s my milk? =) " ; >
> ;

delete cats [ 0 ] ; delete cats [ 1 ] ;
return EXIT_SUCCESS ;
>

Вывод этой программы будет следующим:

Ordinary Cat: Meow! *champing*
Cheshire Cat: WTF?! Where’s my milk? =) *champing*

Рассмотрим код более подробно. Есть класс Cat с парой виртуальных методов, один из которых переопределен в производном CheshireCat. Но всё самое интересное происходит в методе askForFood() класса Cat.

Как видно, метод всего лишь содержит вызовы двух других методов, однако конструкция speak() в данном контексте эквивалента this->speak(), то есть вызов происходит через указатель, а значит — будет использовано позднее связывание. Вот почему при вызове метода askForFood() через указатель на CheshireCat мы видим то, что и хотели: механизм виртуальных функций работает исправно даже несмотря на то, что вызов непосредственно виртуального метода происходит внутри другого метода класса.

А теперь самое интересное: что будет, если попытаться воспользоваться этим в деструкторе? Модернизируем код так, чтобы при деструкции наши питомцы прощались, кто как умеет:

using std :: cout ;
using std :: endl ;

class Cat
<
public :
virtual

Cat ( ) < sayGoodbye ( ) ; >
void askForFood ( ) const
<
speak ( ) ;
eat ( ) ;
>
virtual void speak ( ) const < cout "Meow! " ; >
virtual void eat ( ) const < cout "*champing*" endl ; >
virtual void sayGoodbye ( ) const < cout "Meow-meow!" endl ; >
> ;

class CheshireCat : public Cat
<
public :
virtual void speak ( ) const < cout "WTF?! Where \' s my milk? =) " ; >
virtual void sayGoodbye ( ) const < cout "Bye-bye! (:" endl ; >
> ;

delete cats [ 0 ] ; delete cats [ 1 ] ;
return EXIT_SUCCESS ;
>

Можно ожидать, что, как и в случае с вызовом метода speak(), будет выполнено позднее связывание, однако это не так:

Ordinary Cat: Meow! *champing*
Cheshire Cat: WTF?! Where’s my milk? =) *champing*
Meow-meow!
Meow-meow!

Почему? Да потому что при вызове виртуальных методов из деструктора компилятор использует не позднее, а раннее связывание. Если подумать, зачем он делает именно так, всё становится очевидным: нужно просто рассмотреть порядок конструирования и разрушения объектов. Все помнят, что конструирование объекта происходит, начиная с базового класса, а разрушение идет в строго обратном порядке. Таким образом, когда мы создаем объект типа CheshireCat, порядок вызовов конструкторов/деструкторов будет таким:

Если же мы захотим внутри деструктора

Cat() совершить виртуальный вызов метода sayGoodbye(), то фактически попытаемся обратиться к той части объекта, которая уже была разрушена.

Мораль: если в вашей голове витают помыслы выделить какой-то алгоритм «зачистки» в отдельный метод, переопределяемый в производных классах, а затем виртуально вызывать его в деструкторе, у вас ничего не выйдет.

Источник

Урок №165. Виртуальные деструкторы и Виртуальное присваивание

Обновл. 15 Сен 2021 |

Хотя язык C++ автоматически предоставляет деструкторы для ваших классов, если вы не предоставляете их самостоятельно, все же иногда вы можете сделать это сами.

Виртуальные деструкторы

При работе с наследованием ваши деструкторы всегда должны быть виртуальными. Рассмотрим следующий пример:

Parent ( ) // примечание: Деструктор не виртуальный

Child ( ) // примечание: Деструктор не виртуальный

Поскольку parent является указателем класса Parent, то при его уничтожении компилятор будет смотреть, является ли деструктор класса Parent виртуальным. Поскольку это не так, то компилятор вызовет только деструктор класса Parent.

Результат выполнения программы:

Тем не менее, нам нужно, чтобы delete вызывал деструктор класса Child (который, в свою очередь, будет вызывать деструктор класса Parent), иначе m_array не будет удален. Это можно выполнить, сделав деструктор класса Parent виртуальным:

Parent ( ) // примечание: Деструктор виртуальный

Child ( ) // примечание: Деструктор виртуальный

Результат выполнения программы:

Правило: При работе с наследованием ваши деструкторы должны быть виртуальными.

Виртуальное присваивание

Оператор присваивания можно сделать виртуальным. Однако, в отличие от деструктора, виртуальное присваивание не всегда является хорошей идеей. Почему? Это уже выходит за рамки этого урока. Следовательно, для сохранения простоты в вашем коде, не рекомендуется использовать виртуальное присваивание.

Игнорирование виртуальных функций

В языке С++ мы можем игнорировать вызов переопределений. Например:

Источник

Деструкторы (C++)

). Например, деструктор для класса String объявляется следующим образом:

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

Рассмотрим следующее объявление класса String :

В предыдущем примере деструктор String::

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

Объявление деструкторов

Деструкторы — это функции с тем же именем, что и класс, но с добавленным в начало знаком тильды (

При объявлении деструкторов действуют несколько правил. Деструкторы:

Не могут иметь аргументов.

Не возвращают значение (или void ).

Использование деструкторов

Деструкторы вызываются, когда происходит одно из следующих событий:

Локальный (автоматический) объект с областью видимости блока выходит за пределы области видимости.

Время существования временного объекта заканчивается.

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

Деструктор явно вызываться с использованием полного имени функции деструктора.

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

Существуют два ограничения на использование деструкторов.

Вы не можете получить его адрес.

Производные классы не наследуют деструктор своего базового класса.

Порядок уничтожения

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

Вызывается деструктор класса, и выполняется тело функции деструктора.

Деструкторы для объектов нестатических членов вызываются в порядке, обратном порядку их появления в объявлении класса. Необязательный список инициализации элементов, используемый при создании этих элементов, не влияет на порядок создания или уничтожения.

Деструкторы для невиртуальных базовых классов вызываются в обратную последовательность объявления.

Деструкторы для виртуальных базовых классов вызываются в порядке, обратном порядку их объявления.

Виртуальные базовые классы

Деструкторы для виртуальных базовых классов вызываются в порядке, обратном их указанию в направленном ациклическом графе (в глубину, слева направо, обход в обратном порядке). На следующем рисунке представлен граф наследования.

зачем нужны виртуальные деструкторы. vc392j1. зачем нужны виртуальные деструкторы фото. зачем нужны виртуальные деструкторы-vc392j1. картинка зачем нужны виртуальные деструкторы. картинка vc392j1.
Граф наследования, показывающий виртуальные базовые классы

Ниже перечислены заголовки классов, представленных на рисунке.

Просмотрите левую часть графа, начиная с самой глубокой точки графа (в данном случае E ).

Просматривайте граф справа налево, пока не будут пройдены все узлы. Запомните имя текущего узла.

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

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

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

Просмотрите граф вверх и вдоль следующего пути вправо.

Перейдите к шагу 2.

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

Перейдите к шагу 3.

Выполняйте этот процесс, пока нижний узел снова не станет текущим узлом.

Таким образом, для класса E порядок удаления будет следующим.

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

Порядок построения или удаления очень важен, когда конструкторы и деструкторы в одном классе полагаются на другой компонент, который создается первым или сохраняется дольше, например если деструктор A (на рисунке выше) полагается на то, что B будет по-прежнему присутствовать после выполнения кода, или наоборот.

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

Не являющиеся виртуальными базовыми классами

Деструкторы для невиртуальных базовых классов вызываются в порядке, в котором объявляются имена базовых классов. Рассмотрим следующее объявление класса.

Явные вызовы деструктора

Редко возникает необходимость в явном вызове деструктора. Однако может быть полезно выполнить удаление объектов, размещенных по абсолютным адресам. Обычно эти объекты выделяются с помощью определяемого пользователем new оператора, принимающего аргумент размещения. delete Оператор не может освободить эту память, так как она не выделена из бесплатного хранилища (Дополнительные сведения см. в разделе операторы new и DELETE). Вызов деструктора, однако, может выполнить соответствующую очистку. Для явного вызова деструктора для объекта ( s ) класса String воспользуйтесь одним из следующих операторов.

Нотация для явных вызовов деструкторов, показанная в предыдущем примере, может использоваться независимо от того, определяет ли тип деструктор. Это позволяет выполнять такие явные вызовы, не зная, определен ли деструктор для типа. Явный вызов деструктора, если ни один из них не определен, не имеет никакого эффекта.

Отказоустойчивость

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

Если эти специальные функции не определены пользователем, они неявно определяются компилятором. Неявно созданные конструкторы и операторы присваивания выполняют поверхностную почленном копию, которая почти наверняка неверно, если объект управляет ресурсом.

Явное определение деструктора, конструктора копирования или оператора присваивания копирования предотвращает неявное определение конструктора перемещения и оператора присваивания перемещения. В этом случае не удастся предоставить операции перемещения, если копирование занимает много ресурсов, но пропущенная возможность оптимизации.

Источник

Зачем нам нужен чистый виртуальный деструктор на C++?

Я понимаю необходимость виртуального деструктора. Но зачем нам нужен чистый виртуальный деструктор? В одной из статей на C++ автор упомянул, что мы используем чистый виртуальный деструктор, когда хотим сделать абстрактный класс.

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

когда мы действительно делаем деструктор чисто виртуальным? Может ли кто-нибудь дать хорошее Реальное время пример?

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

12 ответов

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

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

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

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

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

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

все, что вам нужно для абстрактного класса, это по крайней мере одна чистая виртуальная функция. Любая функция будет делать; но, как это происходит, деструктор-это то, что любой класса-так это всегда есть в качестве кандидата. Кроме того, создание деструктора чисто виртуальным (в отличие от просто виртуального) не имеет поведенческих побочных эффектов, кроме как сделать класс абстрактным. Таким образом, многие руководства по стилю рекомендуют использовать чистый виртуальный destuctor последовательно, чтобы указать, что класс является абстрактным-если только по какой-то причине он обеспечивает согласованное место, кто-то, читающий код, может посмотреть, является ли класс абстрактным.

Если вы хотите создать абстрактный базовый класс:

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

для нашей гипотетической азбуки:

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

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

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

в моем мнение, чистые виртуальные деструкторы могут быть полезны. Например, предположим, что в коде есть два класса myClassA и myClassB и что myClassB наследуется от myClassA. По причинам, упомянутым Скоттом Мейерсом в его книге «более эффективный C++», пункт 33 «сделать не-листовые классы абстрактными», лучше фактически создать абстрактный класс myAbstractClass, от которого наследуют myClassA и myClassB. Это обеспечивает лучшую абстракцию и предотвращает некоторые проблемы, возникающие, например, с объектом копии.

в процессе абстракции (создания класса myAbstractClass) может быть, что ни один метод myClassA или myClassB не является хорошим кандидатом на то, чтобы быть чистым виртуальным методом (что является предпосылкой для того, чтобы myAbstractClass был абстрактным). В этом случае вы определяете деструктор абстрактного класса pure virtual.

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

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

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

когда вам не нужно над вещью, только вам нужен сейф уничтожение объекта производного класса

базы* сайте pbase = новый производный(); удалить сайте pbase; чистый виртуальный деструктор не требуется, только виртуальный деструктор будет выполнять эту работу.

основные отношения объектно-ориентированного дизайна два: Есть-А и есть-А. Я их не выдумывал. Вот как они называются.

IS-a указывает, что конкретный объект идентифицируется как класс, который находится над ним в иерархии классов. Объект банана-это фруктовый объект, если он является подклассом плода класс. Это означает, что везде, где можно использовать фруктовый класс, можно использовать банан. Однако это не рефлексивно. Вы не можете заменить базовый класс для определенного класса, если этот конкретный класс вызывается.

Has-a указывает, что объект является частью составного класса и что существует отношение собственности. Это означает, что в C++ это объект-член, и как таковой onus находится на классе-владельце, чтобы избавиться от него или передать право собственности перед разрушением себя.

эти два понятия легче реализовать в языках с одним наследованием, чем в модели множественного наследования, такой как C++, но правила по существу одинаковы. Осложнение возникает, когда идентификатор класса неоднозначен, например, передача указателя класса Banana в функцию, которая принимает указатель класса Fruit.

виртуальные функции, во-первых, вещь времени выполнения. Это часть полиморфизма в том, что он используется для решения, какую функцию запускать в то время, когда он вызывается запущенная программа.

ключевое слово virtual-это директива компилятора для привязки функций в определенном порядке, если существует неопределенность относительно идентификатора класса. Виртуальные функции всегда находятся в родительских классах (насколько мне известно) и указывают компилятору, что привязка функций-членов к их именам должна происходить сначала с функцией подкласса, а затем с функцией родительского класса.

фруктовый класс может иметь виртуальную функцию color (), которая возвращает » NONE» по умолчанию. Функция Banana class color() возвращает «желтый» или «коричневый».

но если функция, принимающая фруктовый указатель, вызывает color () в отправленном ей классе Banana-какая функция color () вызывается? Функция обычно вызывает Fruit:: color() для объекта Fruit.

это было бы 99% времени не то, что предполагалось. Но если Fruit:: color () был объявлен виртуальным, то Banana:color () будет вызван для объекта, потому что правильная функция color () будет будьте привязаны к указателю Fruit во время вызова. Среда выполнения проверяет, на какой объект указывает указатель, поскольку он был помечен как виртуальный в определении класса Fruit.

это отличается от переопределения функции в подклассе. В таком случае фруктовый указатель вызовет Fruit:: color() если все, что он знает, это то, что он-указатель на фрукты.

Итак, теперь возникает идея «чистой виртуальной функции». Это довольно неудачная фраза, поскольку чистота не имеет ничего общего с ним. Это означает, что предполагается, что метод базового класса никогда не называли. Действительно чисто виртуальная функция не может быть вызвана. Однако его все еще необходимо определить. Должна существовать сигнатура функции. Многие кодеры делают пустую реализацию <> для полноты, но компилятор будет генерировать ее внутри, если нет. В том случае, когда функция вызывается, даже если указатель на фрукты, Banana:: color() будет вызываться, поскольку это единственная реализация color () там есть.

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

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

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

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

таким образом, вам запрещено в этом случае создавать экземпляры фруктов, но разрешено создавать экземпляры банана.

вызов для удаления фруктового указателя, указывающего на экземпляр Банан сначала вызовет Banana::

Banana(), а затем вызовет Fuit::

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

это плохая модель? Это сложнее на этапе проектирования, да, но это может гарантировать, что правильная компоновка выполняется во время выполнения и что функция подкласса выполняется там, где есть неопределенность относительно того, к какому подклассу осуществляется доступ.

Если вы пишете C++ так, что вы только передайте точные указатели класса без общих или неоднозначных указателей, тогда виртуальные функции действительно не нужны. Но если вам требуется гибкость во время выполнения типов (как в Apple Banana Orange ==> Fruit), функции становятся проще и универсальнее с меньшим количеством избыточного кода. Вам больше не нужно писать функцию для каждого типа фруктов, и вы знаете, что каждый фрукт будет реагировать на color() со своей правильной функцией.

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

вы попросили пример, и я считаю, что следующее дает причину для чистого виртуального деструктора. Я с нетерпением жду ответов на вопрос, является ли это хороший причина.

Я не хочу, чтобы кто-то мог кинуть error_base type, но типы исключений error_oh_shucks и error_oh_blast имеют идентичную функциональность, и я не хочу писать ее дважды. Сложность pImpl необходима, чтобы избежать разоблачения std::string моим клиентам и использование std::auto_ptr требуется конструктор копирования.

открытый заголовок содержит спецификации исключений, которые будут доступны клиенту для различения различных типов исключений, создаваемых моей библиотекой:

и вот общая реализация:

класс exception_string, закрытый, скрывает std:: string из моего открытого интерфейса:

мой код затем выдает ошибку как:

в Использование шаблона для error немного дармовой. Это экономит немного кода за счет требования клиентов ловить ошибки как:

может есть другой РЕАЛЬНЫЙ СЛУЧАЙ чистого виртуального деструктора, который я на самом деле не вижу в других ответах 🙂

во-первых, я полностью согласен с отмеченным ответом: это потому, что запрещение чистого виртуального деструктора потребует дополнительного правила в спецификации языка. Но это все еще не тот случай использования, который Марк призывает:)

сначала представьте себе это:

просто-у нас есть интерфейс Printable и какой-то» контейнер», содержащий что-либо с этим интерфейсом. Думаю, здесь вполне понятно почему print() метод чисто виртуальным. Он может иметь некоторое тело, но в случае отсутствия реализации по умолчанию, pure virtual является идеальной » реализацией «(=»должен быть предоставлен классом потомка»).

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

и там же контейнер:

Это тема десятилетней давности 🙂 Прочитайте последние 5 абзацев пункта №7 в книге » Эффективный C++ «для деталей, начиная с» иногда может быть удобно дать классу чистый виртуальный деструктор. «

1) Если вы хотите, чтобы производные классы выполняли очистку. Это редкость.

2) нет,но вы хотите, чтобы он был виртуальным.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *