Как реализован 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;
Другие вопросы по тегам