Как реализован dynamic_cast
Рассмотрим эту простую иерархию:
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
был объявлен в другой библиотеке)?
Я ищу конкретные детали о том, как это на самом деле работает (желательно в GCC, но другие тоже хорошо). Этот вопрос не является дубликатом этого вопроса (который не определяет, как он на самом деле работает).
3 ответа
Как можно
dynamic_cast
знать лиDerived2
был получен изDerived
(что, еслиDerived
был объявлен в другой библиотеке)?
Ответ на это удивительно прост: dynamic_cast
может знать это, сохраняя эти знания.
Когда компилятор генерирует код, он хранит данные об иерархиях классов в некоторой таблице, которая dynamic_cast
можно посмотреть позже. Эта таблица может быть присоединена к указателю vtable для легкого поиска dynamic_cast
реализация. Данные, необходимые для typeid
для этих классов также могут быть сохранены вместе с теми.
Если задействованы библиотеки, такого рода вещи обычно требуют раскрытия информационных структур этих типов в библиотеках, как и в функциях. Можно, например, получить ошибку компоновщика, которая выглядит как "Неопределенная ссылка на" vtable for XXX "" (и "мальчик, это раздражает!"), Опять же, как с функциями.
Магия.
Просто шучу. Если вы действительно хотите исследовать это подробно, код, который реализует его для GCC, находится на libsupC++, части libstdC++.
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
Здесь подробно описывается формат информации о типах, которую генерирует компилятор, и он должен дать вам подсказку, как поддержка времени выполнения затем находит правильный путь приведения.
Как dynamic_cast может узнать, был ли Derived2 получен из Derived (что, если Derived был объявлен в другой библиотеке)?
dynamic_cast
Сам не знает ничего, его компилятор знает эти факты. 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;