виртуальная функция - это runtime понятие

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

виртуальная функция - это такая функция, что будучи вызванной у объекта по ссылке/указателю на родителя, она всё равно работает, как версия от наследника.

виртуальная функция - это такая функция, что решение какую версию выбрать принимается в рантайме, а не в компайл тайме

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

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

деструкторы по умолчанию не виртуальные, потому что это дорого (vtable)

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

поэтому важно писать override (C++11) после сигнатуры, чтобы сказать компилятору, что мы хотели переопределить виртуальную функцию из родителей. если в родителях нету такой виртуальной функции, то CE. не влияет на переопределение, влияет лишь на добавление ошибки компиляции при несовпадении сигнатур

ещё для виртуальных функций можно написать final (С++11). это будет означать запрет всем наследникам переопределять эту функцию, то есть определять её с такой же сигнатурой позволяет компилятору делать некоторые оптимизации final влечёт override, так что не нужно писать override final из слов virtual, override и final всегда нужно максимум одно

override, final - contex dependence key words (контекстно зависимые ключевые слова) override не является встроенным в язык ключевым словом, оно несёт смысл только в определенном контексте. поэтому можно объявить переменную или функцию с названием override. это так, потому что эти слова добавились позже, чем вышел первый стандарт языка, и для того, чтобы старый код не поломался, если бы там были такие переменные/функции

dynamic_cast

dynamic_cast - каст в рантайме, работает только для типов с виртуальными функциями, потому что для типов, у которых нет виртуальных функций нет способа узнать, что это за тип на самом деле полиморфность типа это необходимое условие для того, чтобы dynamic_cast мог проверить в рантайме, что это за тип на самом деле (полиморфный должен быть тот, от кого делается каст, а не к кому) отличие от static_cast: если при касте ссылки на base к ссылке на derived в ссылке на base на самом деле не derived, то dynamic_cast выдаст эксепшн std::bad_cast, а static_cast UB если скастить указатель на base к указателю на derived и под этим указателем лежит на самом деле base, то dynamic_cast выдаст nullptr можно всегда делать dynamic_cast от любого полиморфного типа к void* можно всегда делать dynamic_cast верх даже если тип не полиморфный dynamic_cast может сделать каст вбок (каст вбок - каст от ребенка по указателю мамы на указатель папы)

неполиморфные типы можем кастить dynamic_cast’ом - только вверх! (static_cast’ом тоже можно)

оператор typeid() от произвольной переменной возвращает тип std::type_info у этого типа есть оператор сравнения == и метод .name(), который возвращает стринг с названием типа (манглированное имя (mangled ~ закодированное компилятором)) для полиморфных типов выводит тип, которым он реально является, а не от типа, от которого вызвались

RTTI - RunTime Time Information механизм RTTI - механизм позволяющий в рантайме обновлять информацию, какого типа является объект реализуется через vtable’ы, в которых помимо указателей на витуальные функции есть инфа о типе объекта - строка с именем, нужная для typeid()

неочевидные проблемы с виртуальными функциями

нельзя сделать static virtual - CE

нельзя оставить виртуальную функцию без определения, даже если её не вызываем исключение pure virtual если не определим, то ошибка линкера, тк компилятор должен сгенерировать vtable, в который он должен положить адрес этой функции

если вызвать из конструктора/деструктора/списка инициализации виртуальную функцию, то механизм работы виртуальных функций отключается, она воспринимается, как обычная, чтобы избежать UB однако если засунуть в конструктор метод, вызывающий абстрактную функцию, то механизм виртуальных функций будет работать и можно словить pure virtual function called вызов pure virtual метода в конструкторах или деструкторах приводит к UB. в vtable абстрактных функций лежит заглушка - поинтер на функцию, которая выводит текст pure virtual method called и делает аборт (std::terminate)

напишется “Derived 1”, тк в compile time решается, какое значение примет х

указатель на виртуальный метод состоит из 2 частей и весит 16 байт (как и указатель на метод) первая часть (8 байт) показывает сдвиг от начала vtable, где искать адрес метода. последним битом ставится 1, чтобы отличать от указателей на обычные методы, поэтому всегда нечётные вторая часть показывает сдвиг относительно начала объекта

в функции h создается объект родителя и у него вызывается функция f. эта функция делает placement new на себя же, подкладывая вместо себя наследника и возвращает 2. далее мы вызываем от того же объекта, который теперь посути наследник виртуальную функцию f, но вызовется родительская версия. потому что компилятор считает, что тип объекта не может изменится от вызова от него методов (оптимизация девиртуализации, выведется 4)

однако вот так выведется 3 но так делать все равно нельзя, оба примера - UB крч у компилятора есть формальное право считать, что по одному и тому же адресу не кладётся новый тип