print_total(cout, derived, 10); //
В первом вызове параметр item
связан с объектом типа Quote
. В результате, когда функция print_total()
вызовет функцию net_price()
, выполнится ее версия, определенная в классе Quote
. Во втором вызове параметр item
связан с объектом класса Bulk_quote
. В этом вызове функция print_total()
вызывает версию функции net_price()
класса Bulk_quote
.
Крайне важно понимать, что динамическое связывание происходит только при вызове виртуальной функции через указатель или ссылку.
base = derived; //
base.net_price(20); //
Когда происходит вызов виртуальной функции в выражении с обычным типом (не ссылкой и не указателем), такой вызов привязывается во время компиляции. Например, когда происходит вызов функции net_price()
объекта base
, нет никаких вопросов о выполняемой версии. Можно изменить значение (т.е. содержимое) объекта, который представляет base
, но нет никакого способа изменить тип этого объекта. Следовательно, этот вызов распознается во время компиляции как версия Quote::net_price()
.
Одной из ключевых концепций ООП является
Когда при помощи ссылки или указателя на базовый класс происходит вызов функции, определенной в базовом классе, точный тип объекта, для которого будет выполняться функция, неизвестен. Это может быть объект базового класса, а может быть и производного. Если вызываемая функция не виртуальна, независимо от фактического типа объекта, выполнена будет та версия функции, которая определена в базовом классе. Если функция виртуальна, решение о фактически выполняемой версии функции откладывается до времени выполнения. Она определяется на основании типа объекта, с которым связана ссылка или указатель.
С другой стороны, вызовы невиртуальных функций связываются во время компиляции. Точно так же вызовы любой функции (виртуальной или нет) для объекта связываются во время компиляции. Тип объекта фиксирован и неизменен — никак нельзя заставить динамический тип объекта отличаться от его статического типа. Поэтому вызовы для объекта связываются во время компиляции с версией, определенной типом объекта.
При переопределении виртуальной функции производный класс может, но не обязан, повторить ключевое слово virtual
. Как только функция объявляется виртуальной, она остается виртуальной во всех производных классах.
У функции производного класса, переопределяющей унаследованную виртуальную функцию, должны быть точно такие же типы параметров, как и у функции базового класса, которую она переопределяет.