Как реализована динамическая реализация
Рассмотрим эту простую иерархию:
class Base { public: virtual ~Base() { } };
class Derived : public Base { };
Попытка спускать Base* p
в Derived*
возможна с помощью dynamic_cast<Derived*>(p)
. Я думал, что dynamic_cast
работает, сравнивая указатель vtable в p
с тем, что находится в объекте Derived
.
Но что, если мы выведем другой класс из Derived
? Теперь имеем:
class Derived2 : public Derived { };
В этом случае:
Base* base = new Derived2;
Derived* derived = dynamic_cast<Derived*>(base);
Мы по-прежнему получаем успешное нажатие, хотя указатель vtable в Derived2
не имеет ничего общего с указателем vtable в Derived
.
Как это работает? Как может dynamic_cast
знать, был ли Derived2
получен из Derived
(что, если Derived
было объявлено в другой библиотеке)?
I am ищет конкретные сведения о том, как это работает (желательно в GCC, но другие тоже прекрасны). Этот вопрос не дубликат этого вопроса (который не указывает, как он работает).
Ответы
Ответ 1
Как dynamic_cast
знает, был ли Derived2
получен из Derived
(что, если Derived
было объявлено в другой библиотеке)?
Ответ на это на удивление прост: dynamic_cast
может это знать, сохраняя это знание.
Когда компилятор генерирует код, он хранит данные об иерархиях классов в какой-то таблице, которые dynamic_cast
может искать позже. Эта таблица может быть прикреплена к указателю vtable для удобного поиска с помощью реализации dynamic_cast
. Данные, загруженные для typeid
для этих классов, также могут храниться вместе с ними.
Если библиотеки задействованы, такого рода вещи обычно требуют, чтобы эти информационные структуры типа отображались в библиотеках, как и функции. Возможно, например, получить ошибку компоновщика, которая выглядит как "Undefined ссылка на" vtable для XXX "(и мальчик, это раздражает!), Опять же, как и с функциями.
Ответ 2
Магия.
Просто шучу. Если вы действительно хотите подробно изучить это, код, который реализует его для GCC, находится в libsupС++, части libstdС++.
https://github.com/mirrors/gcc/tree/master/libstdc%2B%2B-v3/libsupc%2B%2B
В частности, найдите все файлы с tinfo или type_info в их имени.
Или прочитайте описание здесь, что, вероятно, намного более доступно:
https://itanium-cxx-abi.github.io/cxx-abi/abi.html#rtti
Здесь подробно описывается формат информации о типе, которую генерирует компилятор, и должен дать вам понять, как поддержка времени выполнения находит правильный путь каста.
Ответ 3
Как может dynamic_cast узнать, был ли Derived2 получен из Derived (что, если Derived было объявлено в другой библиотеке)?
Сам dynamic_cast
ничего не знает, его компилятор знает эти факты. Vtable не обязательно содержит только указатели на виртуальные функции.
Вот как бы я это сделал (наивно): my vtable будет содержать указатель к некоторой информации о типе (RTTI), используемой dynamic_cast
. RTTI для типа будет содержать указатели на базовые классы, поэтому я могу подняться по иерархии классов. Псевдокод для роли будет выглядеть следующим образом:
Base* base = new Derived2; //base->vptr[RTTI_index] points to RTTI_of(Derived2)
//dynamic_cast<Derived*>(base):
RTTI* pRTTI = base->vptr[RTTI_index];
while (pRTTI && *pRTTI != RTTI_of(Derived))
{
pRTTI = pRTTI->ParentRTTI;
}
if (pRTTI) return (Derived*)(base);
return NULL;