Я изучаю C ++, и я только вхожу в виртуальные функции.
Из того, что я прочитал (в книге и онлайн), виртуальные функции - это функции в базовом классе, которые вы можете переопределить в производных классах.
Но ранее в книге, изучая базовое наследование, я смог переопределить базовые функции в производных классах без использования virtual
.
Так чего мне здесь не хватает? Я знаю, что есть еще что-то для виртуальных функций, и это кажется важным, поэтому я хочу уточнить, что это такое. Я просто не могу найти прямой ответ в Интернете.
c++
virtual-functions
Джейк Уилсон
источник
источник
Ответы:
Вот как я понял не только то, что
virtual
функции, но почему они необходимы:Допустим, у вас есть эти два класса:
В вашей основной функции:
Пока все хорошо, правда? Животные едят непатентованную пищу, кошки едят крыс, все без
virtual
.Давайте теперь немного его изменим, чтобы
eat()
он вызывался через промежуточную функцию (тривиальная функция только для этого примера):Теперь наша основная функция:
О-о ... мы передали Кота
func()
, но он не будет есть крыс. Стоит ли перегрузить,func()
чтобы это занялоCat*
? Если вам нужно извлечь больше животных из Animal, им всем понадобится свое собственноеfunc()
.Решение состоит в том, чтобы сделать
eat()
изAnimal
класса виртуальную функцию:Главный:
Выполнено.
источник
virtual
вводит некоторое динамическое связывание против статического, и да, это странно, если вы пришли из таких языков, как Java.Без «виртуального» вы получаете «раннее связывание». Какая реализация метода используется, определяется во время компиляции на основе типа указателя, через который вы вызываете.
С «виртуальным» вы получаете «позднее связывание». Какая реализация метода используется, определяется во время выполнения в зависимости от типа указываемого объекта - как он был изначально создан. Это не обязательно то, что вы думаете, основываясь на типе указателя, который указывает на этот объект.
РЕДАКТИРОВАТЬ - см. Этот вопрос .
Также - этот урок охватывает раннее и позднее связывание в C ++.
источник
main
функции и т. Д. Неявно приводимые к указателю на неявно приводят к указателю на основание (более специализированные неявно приводят к более общим). Наоборот, вам нужен явный актерский состав, обычно adynamic_cast
. Все остальное - очень склонное к неопределенному поведению, поэтому убедитесь, что вы знаете, что делаете. Насколько я знаю, это не изменилось с тех пор даже C ++ 98.Вам нужен как минимум 1 уровень наследования и принижение, чтобы продемонстрировать это. Вот очень простой пример:
источник
Вам нужны виртуальные методы для безопасного даункинга , простоты и лаконичности .
Это то, что делают виртуальные методы: они снижают безопасность, используя, по-видимому, простой и лаконичный код, избегая небезопасного ручного приведения в более сложный и подробный код, который вы могли бы иметь в противном случае.
Не виртуальный метод ⇒ статическое связывание
Следующий код намеренно «неверен». Он не объявляет
value
метод какvirtual
, и поэтому выдает непреднамеренный «неправильный» результат, а именно 0:В строке, отмеченной как «плохой»,
Expression::value
вызывается метод, потому что статически известный тип (тип, известный во время компиляции) естьExpression
, аvalue
метод не является виртуальным.Виртуальный метод ⇒ динамическое связывание.
Объявление
value
какvirtual
в статически известном типеExpression
гарантирует, что каждый вызов проверит, какой это фактический тип объекта, и вызовет соответствующую реализациюvalue
для этого динамического типа :Здесь вывод такой,
6.86
каким он должен быть, поскольку виртуальный метод вызывается виртуально . Это также называется динамическим связыванием вызовов. Выполняется небольшая проверка, находим фактический динамический тип объекта и вызывается соответствующая реализация метода для этого динамического типа.Соответствующая реализация относится к наиболее конкретному (наиболее производному) классу.
Обратите внимание, что реализации методов в производных классах здесь не помечены
virtual
, а помеченыoverride
. Они могут быть помечены,virtual
но они автоматически виртуальные. Вoverride
ключевых слов гарантирует , что если есть не такой виртуальный метод в каком - то базовом классе, то вы получите сообщение об ошибке (что желательно).Безобразие делать это без виртуальных методов
Без этого
virtual
пришлось бы реализовать некоторую версию динамического связывания Do It Yourself . Именно это, как правило, связано с небезопасным ручным понижением, сложностью и многословностью.Для случая отдельной функции, как здесь, достаточно сохранить указатель на функцию в объекте и вызвать ее через этот указатель на функцию, но даже в этом случае требуется небезопасное снижение производительности, сложность и многословность:
Один из положительных способов взглянуть на это так: если вы сталкиваетесь с небезопасным снижением рейтинга, сложностью и многословностью, как описано выше, то зачастую виртуальный метод или методы действительно могут помочь.
источник
Виртуальные функции используются для поддержки полиморфизма времени выполнения .
То есть виртуальное ключевое слово говорит компилятору не принимать решение (о привязке функции) во время компиляции, а скорее отложить его на время выполнения » .
Вы можете сделать функцию виртуальной, предшествуя ключевому слову
virtual
в объявлении базового класса. Например,Когда базовый класс имеет виртуальную функцию-член, любой класс, который наследуется от базового класса, может переопределить функцию с точно таким же прототипом, т. Е. Может быть переопределена только функциональность, а не интерфейс функции.
Указатель базового класса может использоваться для указания на объект базового класса, а также на объект производного класса.
источник
Если базовый класс есть
Base
, а производный класс естьDer
, вы можете иметьBase *p
указатель, который фактически указывает на экземпляр классаDer
. Когда вы звонитеp->foo();
, еслиfoo
это не виртуальная, тоBase
версия «s ее выполняет, не обращая внимания на то , что наp
самом деле указывает наDer
. Если foo является виртуальным,p->foo()
выполняет «крайний лист» переопределенияfoo
, полностью принимая во внимание фактический класс указанного элемента. Таким образом, различие между виртуальным и не виртуальным на самом деле очень важно: первое допускает полиморфизм во время выполнения , основную концепцию ОО-программирования, а второе - нет.источник
Потребность в виртуальной функции объяснена [легко понять]
Выход будет:
Но с виртуальной функцией:
Выход будет:
Следовательно, с помощью виртуальной функции вы можете достичь полиморфизма во время выполнения.
источник
Я хотел бы добавить еще одно использование виртуальной функции, хотя она использует ту же концепцию, что и приведенные выше ответы, но, думаю, ее стоит упомянуть.
Виртуальный разрушитель
Рассмотрим эту программу ниже, не объявляя деструктор Базового класса как виртуальный; память для кошки не может быть очищена.
Вывод:
Вывод:
источник
without declaring Base class destructor as virtual; memory for Cat may not be cleaned up.
Это хуже чем это. Удаление производного объекта через базовый указатель / ссылку является чисто неопределенным поведением. Так что не только утечка памяти. Скорее всего , программа плохо формируется, поэтому компилятор может превратить его в что - нибудь: машинный код , что случается , работает хорошо, или ничего не делает, или вызова демонов из вашего носа, или и т.д., поэтому, если программа предназначена в таком Чтобы какой-то пользователь мог удалить производный экземпляр через базовую ссылку, база должна иметь виртуальный деструкторВы должны различать перегрузку и перегрузку. Без
virtual
ключевого слова вы перегружаете только метод базового класса. Это ничего не значит, кроме сокрытия. Допустим, у вас есть базовый классBase
и производный класс,Specialized
которые оба реализуютvoid foo()
. Теперь у вас есть указатель наBase
указатель на экземплярSpecialized
. Когда вы вызываетеfoo()
его, вы можете наблюдать разницу, котораяvirtual
имеет место: если метод является виртуальным,Specialized
будет использована реализация, если он отсутствует,Base
будет выбрана версия из . Лучше никогда не перегружать методы из базового класса. Создание метода, не являющегося виртуальным, позволяет его автору сказать, что его расширение в подклассах не предназначено.источник
virtual
тебя не перегружаешься. Вы следите . Если базовый классB
имеет одну или несколько функцийfoo
, а производный классD
определяетfoo
имя, котороеfoo
скрывает все эти-foo
вB
. Они достигаются сB::foo
использованием разрешения области. Чтобы продвигатьB::foo
функции вD
перегрузку, вы должны использоватьusing B::foo
.Быстрый ответ:
В Bjarne Stroustrup C ++ Программирование: принципы и практика, (14.3):
1. Использование наследования, полиморфизма во время выполнения и инкапсуляции является наиболее распространенным определением объектно-ориентированного программирования .
2. Вы не можете кодировать функциональность, чтобы быть быстрее или использовать меньше памяти, используя другие языковые функции для выбора среди альтернатив во время выполнения. Бьярн Страуструп C ++ Программирование: принципы и практика. (14.3.1) .
3. Что-то, чтобы сказать, какая функция действительно вызывается, когда мы вызываем базовый класс, содержащий виртуальную функцию.
источник
У меня есть мой ответ в форме беседы, чтобы быть лучше прочитанным:
Зачем нам нужны виртуальные функции?
Из-за полиморфизма.
Что такое полиморфизм?
Тот факт, что базовый указатель также может указывать на объекты производного типа.
Как это определение полиморфизма приводит к необходимости виртуальных функций?
Ну, через раннее связывание .
Что такое раннее связывание?
Раннее связывание (связывание во время компиляции) в C ++ означает, что вызов функции фиксируется перед выполнением программы.
Так...?
Таким образом, если вы используете базовый тип в качестве параметра функции, компилятор распознает только базовый интерфейс, и если вы вызываете эту функцию с любыми аргументами из производных классов, она отсекается, что не должно происходить.
Если это не то, что мы хотим, почему это разрешено?
Потому что нам нужен полиморфизм!
Какая польза от полиморфизма?
Вы можете использовать указатель базового типа в качестве параметра отдельной функции, а затем во время выполнения вашей программы вы можете получить доступ к каждому из интерфейсов производного типа (например, их функции-члены) без каких-либо проблем, используя разыменование этого единственного объекта. базовый указатель.
Я до сих пор не знаю, для чего нужны виртуальные функции ...! И это был мой первый вопрос!
ну, это потому что ты задал свой вопрос слишком рано!
Зачем нам нужны виртуальные функции?
Предположим, что вы вызвали функцию с базовым указателем, у которого был адрес объекта из одного из его производных классов. Как мы говорили об этом выше, во время выполнения этот указатель разыменовывается, но пока все хорошо, однако мы ожидаем, что метод (== функция-член) «из нашего производного класса» будет выполнен! Однако в базовом классе уже определен тот же метод (с тем же заголовком), так почему ваша программа должна выбрать другой метод? Другими словами, я имею в виду, как вы можете отличить этот сценарий от того, что мы обычно видели раньше?
Краткий ответ - «Виртуальная функция-член в базе», и немного более длинный ответ - «на этом этапе, если программа видит виртуальную функцию в базовом классе, она знает (понимает), что вы пытаетесь использовать «полиморфизм» и т. д. переходит к производным классам (используя v-таблицу , форму позднего связывания), чтобы найти этот другой метод с тем же заголовком, но, как ожидается, с другой реализацией.
Почему другая реализация?
Ты с головой! Иди почитай хорошую книгу !
Хорошо, подожди, подожди, подожди, зачем использовать базовые указатели, если он / она может просто использовать указатели производного типа? Будь судьей, стоит ли вся эта головная боль? Посмотрите на эти два фрагмента:
// 1:
// 2:
Хорошо, хотя я думаю, что 1 все еще лучше, чем 2 , вы могли бы написать 1 так же:
// 1:
и, кроме того, вы должны знать, что это все еще надуманное использование всего того, что я вам объяснил до сих пор. Вместо этого, предположим, например, ситуацию, в которой у вас была функция в вашей программе, которая использовала методы из каждого из производных классов соответственно (getMonthBenefit ()):
Теперь попробуйте переписать это без головной боли!
И на самом деле, это может быть еще и надуманным примером!
источник
Когда у вас есть функция в базовом классе, вы можете использовать ее
Redefine
илиOverride
в производном классе.Переопределение метода . В производном классе дается новая реализация метода базового класса. Не облегчает
Dynamic binding
.Переопределение метода :
Redefining
avirtual method
базового класса в производном классе. Виртуальный метод облегчает динамическое связывание .Итак, когда вы сказали:
вы не переопределяли его, так как метод в базовом классе не был виртуальным, скорее вы переопределяли его
источник
Это помогает, если вы знаете основные механизмы. C ++ формализует некоторые методы кодирования, используемые программистами C, «классы» заменяются на «оверлеи» - структуры с общими разделами заголовка будут использоваться для обработки объектов различных типов, но с некоторыми общими данными или операциями. Обычно базовая структура оверлея (общая часть) имеет указатель на таблицу функций, которая указывает на различный набор процедур для каждого типа объекта. C ++ делает то же самое, но скрывает механизмы, то есть C ++,
ptr->func(...)
где func виртуален, как C(*ptr->func_table[func_num])(ptr,...)
виртуален, , где изменения между производными классами - это содержимое func_table. [Не виртуальный метод ptr-> func () просто переводится как mangled_func (ptr, ..).]В результате вам нужно понять базовый класс только для вызова методов производного класса, т. Е. Если подпрограмма понимает класс A, вы можете передать ей указатель производного класса B, тогда будут вызваны виртуальные методы. B, а не A, так как вы проходите через таблицу функций B указывает на.
источник
Ключевое слово virtual сообщает компилятору, что он не должен выполнять раннее связывание. Вместо этого он должен автоматически установить все механизмы, необходимые для выполнения позднего связывания. Для этого типичный компилятор1 создает отдельную таблицу (называемую VTABLE) для каждого класса, содержащего виртуальные функции. Компилятор помещает адреса виртуальных функций для этого конкретного класса в VTABLE. В каждом классе с виртуальными функциями он тайно размещает указатель, называемый vpointer (сокращенно VPTR), который указывает на VTABLE для этого объекта. Когда вы делаете виртуальный вызов функции через указатель базового класса, компилятор незаметно вставляет код для извлечения VPTR и поиска адреса функции в VTABLE, вызывая, таким образом, правильную функцию и вызывая позднее связывание.
Подробнее в этой ссылке http://cplusplusinterviews.blogspot.sg/2015/04/virtual-mechanism.html
источник
В виртуальных силах ключевых слов компилятора выбрать реализацию методы , определенную в объекте класса , а не в указателе класса.
В приведенном выше примере Shape :: getName будет вызываться по умолчанию, если только getName () не определен как виртуальный в базовом классе Shape. Это заставляет компилятор искать реализацию getName () в классе Triangle, а не в классе Shape.
Виртуальная таблица представляет собой механизм , в котором компилятор отслеживает различных реализации виртуального метода подклассов. Это также называется динамической диспетчеризацией, и есть некоторые накладные расходы , связанные с ним.
Наконец, почему виртуальная среда даже необходима в C ++, почему бы не сделать ее поведение по умолчанию, как в Java?
источник
Зачем нам нужны виртуальные функции?
Виртуальные функции избегают ненужной проблемы с типизацией, и некоторые из нас могут спорить, зачем нам нужны виртуальные функции, когда мы можем использовать указатель производного класса для вызова функции, специфичной для производного класса! Ответ таков: это сводит на нет всю идею наследования в большой системе развитие, где наличие одного объекта указателя базового класса является очень желательным.
Давайте сравним ниже две простые программы, чтобы понять важность виртуальных функций:
Программа без виртуальных функций:
ВЫВОД:
Программа с виртуальной функцией:
ВЫВОД:
Внимательно проанализировав оба результата, можно понять важность виртуальных функций.
источник
ООП Ответ: Подтип Полиморфизм
В C ++, виртуальные методы необходимы для реализации полиморфизма , более точно подтипов или подтипа полиморфизма если вы применяете определение из википедии.
Wikipedia, Subtyping, 2019-01-09: В теории языка программирования подтип (также полиморфизм подтипов или полиморфизм включения) является формой полиморфизма типов, в которой подтип является типом данных, который по некоторому понятию связан с другим типом данных (супертипом). заменяемости, означая, что программные элементы, обычно подпрограммы или функции, написанные для работы с элементами супертипа, также могут работать с элементами подтипа.
ПРИМЕЧАНИЕ. Подтип означает базовый класс, а подтип - унаследованный класс.
Дальнейшее чтение относительно полиморфизма подтипа
Технический ответ: динамическая отправка
Если у вас есть указатель на базовый класс, то вызов метода (который объявлен как виртуальный) будет отправлен методу фактического класса создаваемого объекта. Вот как реализуется полиморфизм подтипа C ++.
Дальнейшее чтение Полиморфизм в C ++ и Dynamic Dispatch
Реализация Ответ: Создает запись vtable
Для каждого модификатора, «виртуального» в методах, компиляторы C ++ обычно создают запись в vtable класса, в котором объявлен метод. Так понимают обычные компиляторы C ++. Dynamic Dispatch .
Дальнейшее чтение vtables
Пример кода
Вывод примера кода
Диаграмма классов UML примера кода
источник
Вот полный пример, который иллюстрирует, почему используется виртуальный метод.
источник
Что касается эффективности, виртуальные функции несколько менее эффективны, чем функции раннего связывания.
«Этот механизм виртуального вызова можно сделать почти таким же эффективным, как механизм« обычного вызова функции »(в пределах 25%). Его служебная память занимает один указатель в каждом объекте класса с виртуальными функциями плюс один vtbl для каждого такого класса» [ A тур по С ++ Бьярном Страуструпом]
источник
if(param1>param2) return cst;
где компилятор может уменьшить весь вызов функции в некоторых случаях).Виртуальные методы используются в дизайне интерфейса. Например, в Windows есть интерфейс под названием IUnknown, как показано ниже:
Эти методы оставлены пользователю интерфейса для реализации. Они необходимы для создания и уничтожения определенных объектов, которые должны наследовать IUnknown. В этом случае среда выполнения знает о трех методах и ожидает, что они будут реализованы при вызове. Таким образом, в некотором смысле они действуют как договор между самим объектом и тем, что использует этот объект.
источник
the run-time is aware of the three methods and expects them to be implemented
Поскольку они являются чисто виртуальными, нет никакого способа создать экземпляр классаIUnknown
, и поэтому все подклассы должны реализовывать все такие методы для простой компиляции. Нет опасности не реализовывать их и только узнавать об этом во время выполнения (но, конечно, можно ошибочно реализовать их, конечно!). И вот, сегодня я узнал#define
макрос Windows sa со словомinterface
, предположительно потому, что их пользователи не могут просто (A) увидеть префиксI
в имени или (B) посмотреть на класс, чтобы увидеть его интерфейс. ТьфуЯ думаю, что вы имеете в виду тот факт, что когда метод объявлен виртуальным, вам не нужно использовать ключевое слово «virtual» в переопределениях.
Если вы не используете 'virtual' в объявлении Base для foo, то Derived's foo просто будет его скрывать.
источник
Вот объединенная версия кода C ++ для первых двух ответов.
Два разных результата:
Без виртуального #define он связывается во время компиляции. Animal * ad и func (Animal *) - все они указывают на метод Animal () с названием ().
С #define virtual он связывается во время выполнения. Dog * d, Animal * ad и func (Animal *) указывают / ссылаются на метод say () Dog, поскольку Dog является их типом объекта. Если метод [Dog's say () "woof"] не определен, он будет первым, который ищется в дереве классов, то есть производные классы могут переопределять методы своих базовых классов [Animal's say ()].
Интересно отметить, что все атрибуты класса (данные и методы) в Python являются фактически виртуальными . Поскольку все объекты создаются динамически во время выполнения, нет объявления типа или необходимости для ключевого слова virtual. Ниже приведена версия кода Python:
Выход:
который идентичен виртуальному определению C ++. Обратите внимание, что d и ad - это две разные переменные-указатели, ссылающиеся на один и тот же экземпляр Dog. Выражение (ad is d) возвращает True, и их значения совпадают с < main .Dog object at 0xb79f72cc>.
источник
Вы знакомы с указателями функций? Виртуальные функции представляют собой аналогичную идею, за исключением того, что вы можете легко привязать данные к виртуальным функциям (в качестве членов класса). Не так просто связать данные с указателями на функции. Для меня это главное концептуальное отличие. Многие другие ответы здесь просто говорят "потому что ... полиморфизм!"
источник
Нам нужны виртуальные методы для поддержки «Полиморфизма времени выполнения». Когда вы ссылаетесь на объект производного класса, используя указатель или ссылку на базовый класс, вы можете вызвать виртуальную функцию для этого объекта и выполнить версию функции производного класса.
источник
Суть в том, что виртуальные функции облегчают жизнь. Давайте воспользуемся некоторыми идеями М. Перри и опишем, что произойдет, если у нас не будет виртуальных функций, а вместо этого мы сможем использовать только указатели на функции-члены. В обычной оценке без виртуальных функций имеем:
Итак, это то, что мы знаем. Теперь давайте попробуем сделать это с помощью указателей на функции-члены:
Хотя мы можем делать некоторые вещи с помощью указателей на функции-члены, они не так гибки, как виртуальные функции. Сложно использовать указатель на функцию-член в классе; указатель на функцию-член почти, по крайней мере в моей практике, всегда должен вызываться в основной функции или из функции-члена, как в приведенном выше примере.
С другой стороны, виртуальные функции, хотя они могут иметь некоторые накладные расходы на указатель на функцию, значительно упрощают ситуацию.
РЕДАКТИРОВАТЬ: есть еще один метод, который похож на eddietree: виртуальная функция c ++ против указателя функции-члена (сравнение производительности) .
источник