tgoop.com/cxx95/94
Last Update:
#madskillz
Напиши свой собственный RTTI
RTTI (run-time type information) это некая информация о всех виртуальных классах, которая попадает в бинарник программы.
Благодаря этому работают dynamic_cast<> (приведение типа в run-time) и typeid.
Генерацию RTTI можно выключить флагом компиляции -fno-rtti
.
Пусть у нас есть указатель X* x
(или ссылка X& x
). Указатель может указывать на объект с типом не X
(но это обязательно будет тип-потомок X
).
Есть три варианта приведений типа X
к типу Y
(тип Y
тоже не обязательно реальный тип объекта):Y
- класс-предок X
.
Для этого не требуется никакого RTTI. Такое приведение всегда возможно. Можно было бы обойтись static_cast<> (приведение типа в compile-time).Y
- класс-потомок X
.
В этом случае обычно используют dynamic_cast<>
. Если окажется, что тип объекта не Y
(или не какого-то потомка Y
), то приведения не случится.
Интересно, что если программист совершенно уверен, что приведение возможно, то можно использовать static_cast<>
и не делать run-time проверку. Если окажется, что он был не прав, то получится undefined behaviour X
и Y
никак не связаны между собой.
Такое возможно, если X*
указывает на объект класса Z
:
class Z : public X, public Y {...};Такие касты
dynamic_cast<>
тоже умеет делать.Скорее всего sidecast значит, что в программе есть серьезные ошибки дизайна
Некоторые проекты не используют "официальный" RTTI
Почему так происходит, на примере очень популярных проектов:
-fno-rtti
.В protobuf есть базовый для всех "месседжей" класс
google::protobuf::Message
.Чтобы попробовать сделать downcast от базового класса до класса "месседжа", можно использовать функцию DynamicCastToGenerated.
Как видно по исходнику, если уж нельзя вызвать
dynamic_cast<>
, то используется костыль - суррогат RTTI: сравнение ссылки на "рефлексию" (уникальное описание "месседжа"). Эту ссылку возвращает виртуальный метод.Какие ограничения этого подхода: С
-fno-rtti
доступен только downcast строго на указанный класс - потомок класса Message
.Clang и LLVM имеют большую иерархию типов и делают огромную кучу проверок на типы. Большая часть кода в компиляторах (оптимизации, кодогенерация, ...) завязана на поиск специфических паттернов и операциях на них. Для этого необходимо проверять тип объектов в овер9000 местах, поэтому быстродействие
dynamic_cast
становится узким местом.А быстродействие у
dynamic_cast
сравнительно плохое. Он должен делать обход иерархии наследования и вычисляет путь обхода динамическим образом. Это на несколько порядков медленнее, чем просто вызвать виртуальный метод и что-то сравнить.В документации есть крутая статья, как сделать свой RTTI "почти как в LLVM" - How to set up LLVM-style RTTI for your class hierarchy. Для этого заводится специальный
enum
, и каждый класс реализует статический метод classof
. Вместо обхода иерархии наследования делается один вызов виртуального метода!Какие ограничения этого подхода: Дополнительный код -
enum
, статический метод в каждом классе. Нужно следить за тем, чтобы соответствие между enum
и классами не разломалось (хотя тут могут помочь кодогенераторы). Эта схема работает, только если иерархия классов известна заранее (стандартный C++ RTTI такого не требует).Всего известно три главных аргумента "против RTTI":
dynamic_cast<>
является узким местом в программе. Но это должна быть специфическая программа, как поиск паттернов в структурах с большой иерархией классов... (например, компилятор C++)