Я понимаю необходимость виртуального деструктора. Но зачем нам чистый виртуальный деструктор? В одной из статей C ++ автор упомянул, что мы используем чистый виртуальный деструктор, когда хотим сделать класс абстрактным.
Но мы можем сделать класс абстрактным, сделав любую функцию-член чисто виртуальной.
Так что мои вопросы
Когда мы действительно сделаем деструктор чисто виртуальным? Кто-нибудь может привести хороший пример в реальном времени?
Когда мы создаем абстрактные классы, полезно ли делать деструктор также чисто виртуальным? Если да, то почему?
c++
destructor
pure-virtual
отметка
источник
источник
Ответы:
Вероятно, настоящая причина того, что чистые виртуальные деструкторы разрешены, заключается в том, что их запрещение означало бы добавление еще одного правила к языку, и в этом правиле нет необходимости, поскольку от использования чистого виртуального деструктора не может быть никаких негативных последствий.
Нет, простой старый виртуальный достаточно.
Если вы создаете объект с реализациями по умолчанию для его виртуальных методов и хотите сделать его абстрактным, не заставляя никого переопределять какой-либо конкретный метод, вы можете сделать деструктор чисто виртуальным. Я не вижу в этом особого смысла, но это возможно.
Обратите внимание, что, поскольку компилятор сгенерирует неявный деструктор для производных классов, если автор класса этого не сделает, любые производные классы не будут абстрактными. Следовательно, наличие чистого виртуального деструктора в базовом классе не будет иметь никакого значения для производных классов. Это только сделает базовый класс абстрактным (спасибо за комментарий @kappa ).
Можно также предположить, что каждый производный класс, вероятно, должен иметь определенный код очистки и использовать чистый виртуальный деструктор в качестве напоминания для его написания, но это кажется надуманным (и неисполненным).
Примечание: Деструктор является единственным методом, даже если он является чисто виртуальным имеет иметь реализацию, чтобы Instantiate производных классов (да чисто виртуальные функции могут иметь реализации).
источник
foof::bar
если хотите сами убедиться.Все, что вам нужно для абстрактного класса - это хотя бы одна чисто виртуальная функция. Подойдет любая функция; но, как это бывает, деструктор - это то, что есть у любого класса, поэтому он всегда в качестве кандидата. Более того, превращение деструктора в чисто виртуальный (в отличие от просто виртуального) не имеет никаких поведенческих побочных эффектов, кроме как сделать класс абстрактным. Таким образом, многие руководства по стилю рекомендуют последовательно использовать чистый виртуальный деструктор, чтобы указать, что класс является абстрактным, - если по какой-либо иной причине, кроме как он обеспечивает согласованное место, кто-то, читающий код, может посмотреть, является ли класс абстрактным.
источник
Если вы хотите создать абстрактный базовый класс:
... проще всего сделать класс абстрактным, сделав деструктор чисто виртуальным и предоставив для него определение (тело метода).
Для нашей гипотетической азбуки:
Вы гарантируете, что он не может быть создан (даже внутри самого класса, поэтому частных конструкторов может быть недостаточно), вы получаете виртуальное поведение, которое вы хотите для деструктора, и вам не нужно находить и маркировать другой метод, который не Виртуальная рассылка не требуется как "виртуальная".
источник
Из ответов, которые я прочитал на ваш вопрос, я не смог найти вескую причину для фактического использования чистого виртуального деструктора. Например, следующая причина меня совсем не убеждает:
На мой взгляд, чистые виртуальные деструкторы могут быть полезны. Например, предположим, что в вашем коде есть два класса myClassA и myClassB, и что myClassB наследуется от myClassA. По причинам, упомянутым Скоттом Мейерсом в его книге «Более эффективный C ++», пункт 33 «Создание абстрактных классов, не являющихся листами», лучше на самом деле создать абстрактный класс myAbstractClass, от которого наследуются myClassA и myClassB. Это обеспечивает лучшую абстракцию и предотвращает некоторые проблемы, возникающие, например, с копиями объектов.
В процессе абстракции (создания класса myAbstractClass) может оказаться, что ни один метод myClassA или myClassB не является хорошим кандидатом на то, чтобы быть чистым виртуальным методом (что является обязательным условием для абстрактности myAbstractClass). В этом случае вы определяете деструктор абстрактного класса чисто виртуальный.
Далее конкретный пример из некоторого кода, который я сам написал. У меня есть два класса, Numerics / PhysicsParams, которые имеют общие свойства. Поэтому я позволил им наследовать от абстрактного класса IParams. В этом случае у меня не было абсолютно никакого метода, который мог бы быть чисто виртуальным. Например, метод setParameter должен иметь одинаковое тело для каждого подкласса. Единственный выбор, который у меня был, - сделать деструктор IParams чисто виртуальным.
источник
IParam
для защиты, как было отмечено в некоторых других комментариях.Если вы хотите прекратить создание экземпляров базового класса без внесения каких-либо изменений в уже реализованный и протестированный производный класс, вы реализуете чистый виртуальный деструктор в базовом классе.
источник
Здесь я хочу сказать, когда нам нужен виртуальный деструктор и когда нам нужен чистый виртуальный деструктор
Если вы хотите, чтобы никто не мог создать объект класса Base напрямую, используйте чистый виртуальный деструктор
virtual ~Base() = 0
. Обычно требуется хотя бы одна чисто виртуальная функция, давайте возьмемvirtual ~Base() = 0
эту функцию.Когда вам не нужно вышеуказанное, нужно только безопасное уничтожение объекта класса Derived
Base * pBase = new Derived (); удалить pBase; чистый виртуальный деструктор не требуется, только виртуальный деструктор будет делать эту работу.
источник
Вы получаете гипотезы с этими ответами, поэтому я попытаюсь сделать более простое, более приземленное объяснение для ясности.
Основными отношениями объектно-ориентированного проектирования являются два: IS-A и HAS-A. Я не сделал это. Вот как они называются.
IS-A указывает, что определенный объект идентифицируется как принадлежащий к классу, который находится над ним в иерархии классов. Банановый объект - это фруктовый объект, если он является подклассом фруктового класса. Это означает, что везде, где можно использовать фруктовый класс, можно использовать банан. Это не рефлексивно, хотя. Вы не можете заменить базовый класс для определенного класса, если этот конкретный класс вызывается.
Has-a указывает, что объект является частью составного класса и что существуют отношения собственности. В C ++ это означает, что это объект-член, и поэтому ответственность за его ликвидацию или передачу прав собственности перед уничтожением себя лежит на классе-владельце.
Эти два понятия легче реализовать в языках с одним наследованием, чем в модели множественного наследования, такой как c ++, но правила по сути одинаковы. Сложность возникает, когда идентичность класса неоднозначна, например, передача указателя класса Banana в функцию, которая принимает указатель класса Fruit.
Виртуальные функции - это, во-первых, вещь во время выполнения. Он является частью полиморфизма в том смысле, что он используется для определения, какую функцию запускать во время вызова в запущенной программе.
Ключевое слово virtual - это директива компилятора, связывающая функции в определенном порядке, если существует неопределенность в отношении идентичности класса. Виртуальные функции всегда находятся в родительских классах (насколько я знаю) и указывают компилятору, что связывание функций-членов с их именами должно выполняться сначала с помощью функции подкласса, а после - с функцией родительского класса.
Класс Fruit может иметь виртуальную функцию color (), которая по умолчанию возвращает «NONE». Функция класса Banana color () возвращает «ЖЕЛТЫЙ» или «КОРИЧНЕВЫЙ».
Но если функция, принимающая указатель Fruit, вызывает color () для отправленного ей класса Banana - какая функция color () вызывается? Функция обычно вызывает Fruit :: color () для объекта Fruit.
Это будет 99% времени не быть тем, что предполагалось. Но если Fruit :: color () был объявлен виртуальным, тогда Banana: color () будет вызван для объекта, потому что правильная функция color () будет связана с указателем Fruit во время вызова. Среда выполнения проверит, на какой объект указывает указатель, поскольку он был помечен как виртуальный в определении класса Fruit.
Это отличается от переопределения функции в подклассе. В этом случае указатель Fruit будет вызывать Fruit :: color (), если все, что он знает, это IS-A указатель на Fruit.
Так что теперь к идее «чисто виртуальной функции» подходит. Это довольно неудачная фраза, поскольку чистота не имеет к этому никакого отношения. Это означает, что предполагается, что метод базового класса никогда не будет вызываться. Действительно чисто виртуальная функция не может быть вызвана. Это все еще должно быть определено, как бы то ни было. Подпись функции должна существовать. Многие кодеры создают пустую реализацию {} для полноты, но компилятор сгенерирует ее внутренне, если нет. В том случае, когда функция вызывается, даже если указатель указывает на Fruit, Banana :: color () будет вызвана, так как это единственная имеющаяся реализация color ().
Теперь последний кусок головоломки: конструкторы и деструкторы.
Чистые виртуальные конструкторы полностью запрещены. Это только что вышло.
Но чисто виртуальные деструкторы работают в том случае, если вы хотите запретить создание экземпляра базового класса. Только подклассы могут быть созданы, если деструктор базового класса является чисто виртуальным. соглашение состоит в том, чтобы присвоить это 0.
Вы должны создать реализацию в этом случае. Компилятор знает, что это то, что вы делаете, и удостоверяется, что вы все делаете правильно, или он сильно жалуется, что не может ссылаться на все функции, необходимые для компиляции. Ошибки могут сбивать с толку, если вы не на правильном пути относительно того, как вы моделируете иерархию классов.
Так что вам запрещено в этом случае создавать экземпляры Fruit, но разрешено создавать экземпляры Banana.
Вызов для удаления указателя Fruit, который указывает на экземпляр Banana, сначала вызовет Banana :: ~ Banana (), а затем всегда вызовет Fuit :: ~ Fruit (). Потому что, несмотря ни на что, когда вы вызываете деструктор подкласса, деструктор базового класса должен следовать.
Это плохая модель? Да, это более сложно на этапе проектирования, но оно может гарантировать, что правильное связывание выполняется во время выполнения и что функция подкласса выполняется там, где существует неопределенность в отношении того, к какому именно подклассу осуществляется доступ.
Если вы пишете C ++ так, что вы передаете только точные указатели классов без общих или неоднозначных указателей, то виртуальные функции на самом деле не нужны. Но если вам требуется гибкость типов во время выполнения (как в Apple Banana Orange ==> Fruit), функции становятся проще и универсальнее с меньшим количеством избыточного кода. Вам больше не нужно писать функцию для каждого типа фруктов, и вы знаете, что каждый фрукт будет реагировать на color () своей собственной правильной функцией.
Я надеюсь, что это многословное объяснение укрепляет концепцию, а не путает вещи. Есть много хороших примеров, на которые можно посмотреть, посмотреть достаточно, запустить их, поработать с ними, и вы получите это.
источник
Это тема десятилетней давности :) Прочитайте последние 5 абзацев пункта № 7 книги "Эффективный C ++" для подробностей, начиная с "Иногда бывает удобно дать классу чистый виртуальный деструктор ...."
источник
Вы просили привести пример, и я полагаю, что следующее дает причину для чисто виртуального деструктора. Я с нетерпением жду ответов относительно того, является ли это хорошей причиной ...
Я не хочу , чтобы кто - нибудь в состоянии бросить
error_base
тип, но типы исключенийerror_oh_shucks
иerror_oh_blast
имеют одинаковую функциональность , и я не хочу писать его дважды. Сложность pImpl необходима, чтобы не подвергатьstd::string
моих клиентов воздействию , а использованиеstd::auto_ptr
требует конструктора копирования.Открытый заголовок содержит спецификации исключений, которые будут доступны клиенту для различения различных типов исключений, создаваемых моей библиотекой:
И вот общая реализация:
Класс exception_string, который является приватным, скрывает std :: string от моего открытого интерфейса:
Мой код выдает ошибку как:
Использование шаблона для
error
немного безвозмездно. Это экономит немного кода за счет требования клиентов отлавливать ошибки, как:источник
Может быть, есть еще один НАСТОЯЩИЙ СЛУЧАЙ чистого виртуального деструктора, которого я не вижу в других ответах :)
Сначала я полностью согласен с помеченным ответом: это потому, что для запрета чисто виртуального деструктора потребуется дополнительное правило в спецификации языка. Но это все еще не тот случай использования, к которому призывает Марк :)
Сначала представьте это:
и что-то вроде:
Просто - у нас есть интерфейс
Printable
и некоторый «контейнер», содержащий что-либо с этим интерфейсом. Я думаю, что здесь совершенно ясно, почемуprint()
метод является чисто виртуальным. Он может иметь некоторое тело, но в случае отсутствия реализации по умолчанию, чисто виртуальная является идеальной «реализацией» (= «должен быть предоставлен классом-потомком»).А теперь представьте точно то же самое, за исключением того, что это не для печати, а для уничтожения:
А также может быть похожий контейнер:
Это упрощенный вариант использования из моего реального приложения. Единственная разница здесь в том, что вместо «нормального» был использован «специальный» метод (деструктор)
print()
. Но причина, по которой он является чисто виртуальным, все та же - для метода нет кода по умолчанию. Немного сбивает с толку тот факт, что ДОЛЖЕН быть какой-то деструктор, и компилятор фактически генерирует для него пустой код. Но с точки зрения программиста чистая виртуальность все еще означает: «У меня нет никакого кода по умолчанию, он должен быть предоставлен производными классами».Я думаю, что здесь нет никакой большой идеи, просто больше объяснений, что чистая виртуальность работает действительно единообразно - также для деструкторов.
источник
1) Когда вы хотите, чтобы производные классы выполняли очистку. Это редко.
2) Нет, но вы хотите, чтобы оно было виртуальным.
источник
нам нужно сделать виртуальный деструктор из-за того факта, что если мы не сделаем виртуальный деструктор, то компилятор будет только уничтожать содержимое базового класса, n все производные классы останутся неизменными, компилятор bacuse не будет вызывать деструктор любого другого класс кроме базового класса.
источник
delete
указатель на базовый класс, когда фактически он указывает на его производную.