## 5.4 RTTI and dynamic cast. ```C++ struct Base { int x = 0; virtual void f() {} virtual ~Base() = default; }; struct Derived : Base { int y = 0; void f() override {} }; int main() { Derived d; Base& b = d; dynamic_cast(b); // an example of correct dynamic_cast // if we know, that Base was from Derived, we can (dynamic) cast Base to Derived. // dynamic_cast will throw std::bad_cast if cast failed Derived* pd = dynamic_cast(b) // will return pointer to Derived in case // of success, and nullptr otherwise if (pd) { // OK } } ``` `dynamic_cast` работает только для типов с виртуальными функциями(полиморфных). Для типов у которых нет виртуальных функций нет способов узнать по родителю, что за базовый класс лежит по этому адресу. Для классов с виртуальными функциями поддерживается специальная информация(vtable), который нужен для того, чтобы понять какую функцию вызывать. По ней в рантайме можно восстановить тип. В случае если тип не полиморфный, код с `dynamic_cast` не скомпилируется. Этот механизм называется RTTI(Runtime type information). Для полиморфных объектов можно в рантайме узнать информацию о типе явно. ```C++ std::type_info info = typeid(b); std::cout << info.name() << std::endl; // will print MANGLED type name // std::type_info can(and should) be compared ``` ## 5.5 Memory layout of polymorphic objects То, что будет рассказываться в этом параграфе формально не является частью стандарта C++, но является частью стандарта ABI и по факту почти наверняка так и есть. Полиморфные объекты хранят __указатель на vtable__. vtable(виртуальная таблица) -- это структура данных, хранящаяся в статической памяти одна на тип, которая хранит адреса виртуальных методов и предков, предков мы обсудим чуть позже. ```C++ struct Base { virtual void f() {} void h() {} int x; }; // sizeof(Base) == 16 due to padding, Base is [vptr, int] ``` Что хранится в vtable для Base? Там хранится `type_info` для `Base`, а затем указатель на функцию `f`. По нулевому смещению хранится `Base::f`, информация о типе лежит "сзади". Пусть мы ещё определили `Derived` следующим образом ```C++ struct Derived : Base { void f() override {} virtual void g() {} int y; }; // Derived is [vptr, x(from Base), y] ``` Таблица для Derived выглядит как `[&typeinfo, &f, &g]`, по нулевому смещению находится `f`. Как тогда выглядит вызов виртуальной функции? Пусть у нас есть `b.f()`. Компилятор видит, что она виртуальная, поэтому это не просто `call` по константому адресу, как это было бы, например, при вызове `b.h()`. Поэтому при вызове `b.f()` мы разыменовываем указатель на виртуальную таблицу. Компилятор уже знает, на каком месте в таблице стоит `f`, поэтому он может ее вызвать. Если бы под `b` лежал на самом деле `Derived`, то под `b` был бы указатель на другую таблицу, так и происходит разрешение. `dynamic_cast`, соответственно, использует `type_info` чтобы понять, что ему надо делать. Рассмотрим пример наследования `Son -> Mom -> Granny` с полями `s, m, g`, причем `Granny` не полиморфная, а `Mom` полиморфная. Тогда `dynamic_cast` от `Son` к `Granny` возможен, в памяти `[ptr, g, m, s]` нужно сдвинуть указатель на начало к `g`, так как у `Granny` нет `ptr` в силу того, что она не полиморфна. А вот обратно `dynamic_cast` невозможен, т.к `Granny` не является полиморфным типом. А что происходит при множественном наследовании? ``` / -> Mom -> Granny Son --/ \ -> Dad -> Granny ``` Для простоты положим `Granny` полиморфной, а наследование не виртуальным. Как выглядит `Son`? `[ptr, g, m, ptr(from Dad), g, d, s]`. Для `Dad` нужен второй указатель, так как у `Son` и `Mom` начало общее, а у `Son` и `Dad` уже нет. Положим `Granny` имеет функцию `f`. Вызов `son.f()` неоднозначен. Но если мы переопределим `f` в `Son`, то вызов `f` в любой из двух `Granny` будет приводить к вызову нужной нам `f`, неоднозначности нет. Теперь добавим щепотку виртуального наследования. Если по умолчанию в виртуальной таблице хранится `top_offset` -- то, как далеко мы находимся от начала объекта, то при виртуальном наследовании в таблице хранится ещё `virtual_offset`, по какому смещению хранится предок. Представим, что `Granny` виртуальная. Тогда `Son` выглядит в памяти как `[ptr, m, ptr, d, s, ptr, g]`. Таблица зависит не только от типа, но и от его положения в графе наследований, ведь теперь у нас есть разные смещения(`virtual_offset`) у разных объектов наследующих один и тот же тип. ## 5.7 Non obvious problems with virtual functions Виртуальные функции очевидно не могут быть `static`. Виртуальные функции нельзя оставлять без определения, ведь нужны указатели на нее, чтобы создать vtable, будет ошибка линковки. На доске был какой-то пример, который __часто дают на собесах(c)__, поэтому в данном конспекте он отсутствует. Стоит гуглить pure virtual function call example.