Romeo » 13 апр 2017, 12:40
RTTI по факту реализован через таблицу виртуальных функций. Большинство компиляторов действуют одинаково. Они создают специальный метод для каждого класса, где есть таблица виртуальных методов и помещают его в таблицу под индексом -1. Метод возвращает ссылку на статический объект типа type_info. Таким образом оператору dynamic_cast достаточно вызвать этот скрытый метод и сравнить является ли адрес возвращённого объекта таким же, как адрес указанного типа, к которому мы кастим. Это если на пальцах. На самом деле алгоритм несколько сложнее, так как он ещё умеет выявлять возможность кастинга с наследникам/предкам, с помощью дополнительных вызовов всё тех же неявных методов, но суть от этого не меняется. Отсюда, кстати, становится понятным почему по стандарту запрещён dynamic_cast, если у класса отсутствует таблица виртуальных методов.
Следующий вопрос, это чем лучше метод GetID или dynamic_cast с проверкой на nullptr. GetID однозначно будет работать несколько быстрее, так как не будет выполняться дополнительный код проверки наследников/предков, который зашит в dynamic_cast. Однако, в случае GetID, нам приходится руками создавать новый виртуальный метод, а так же руками его переопределять в каждом наследнике. В результате имеем лишний виртуальный метод и ненужные хлопоты с написанием дополнительного кода в каждом классе. Плата излишне велика. Я бы выбрал RTTI.
Ну и последнее. Зачем я заговорил о методе IsHighPriority. У него есть одно очень важное преимущество. Он уменьшает связность. Оба предыдущих варианта требовали от композитора знаний о ВСЕХ классах. Весь смысл композитора как раз в том, чтобы однородно работать со всеми содержащимися в нём объектами, не зная о том, какой же объект на самом деле скрыт под указателем на базу. Если мы вносим в композитор знания о все классах, то все плюсы полиморфизма просто перестают приносить нам плоды. Ведь если композитор знает о всех типах, то мы можем сделать внутри не один вектор для всех объектов, а много векторов на каждый тип. Затем сделать перегрузки для каждого метода Add, а также написать ручной обход всех векторов в каждом функциональном методе-делегате. После этого мы легко избавимся от виртуальных методов. Код, который получится в результате называют в программировании спагетти-кодом (подробнее на вики). Он громоздок, абсолютно нерасширяем, да и просто это "код с душком" (С) Scott Meyers.
И так, знание обо всех типах - это плохо. В то время как метод IsHighPriority позволяет не знать обо всех типах, а лишь разделить их да два множества - типы с высоким приоритетом и не с высоким. Да, мы платим цену лишнего виртуального метода, как и в случае GetID, но мы понимаем, за что платим. Мы уменьшаем связность кода.
RTTI по факту реализован через таблицу виртуальных функций. Большинство компиляторов действуют одинаково. Они создают специальный метод для каждого класса, где есть таблица виртуальных методов и помещают его в таблицу под индексом -1. Метод возвращает ссылку на статический объект типа type_info. Таким образом оператору dynamic_cast достаточно вызвать этот скрытый метод и сравнить является ли адрес возвращённого объекта таким же, как адрес указанного типа, к которому мы кастим. Это если на пальцах. На самом деле алгоритм несколько сложнее, так как он ещё умеет выявлять возможность кастинга с наследникам/предкам, с помощью дополнительных вызовов всё тех же неявных методов, но суть от этого не меняется. Отсюда, кстати, становится понятным почему по стандарту запрещён dynamic_cast, если у класса отсутствует таблица виртуальных методов.
Следующий вопрос, это чем лучше метод GetID или dynamic_cast с проверкой на nullptr. GetID однозначно будет работать несколько быстрее, так как не будет выполняться дополнительный код проверки наследников/предков, который зашит в dynamic_cast. Однако, в случае GetID, нам приходится руками создавать новый виртуальный метод, а так же руками его переопределять в каждом наследнике. В результате имеем лишний виртуальный метод и ненужные хлопоты с написанием дополнительного кода в каждом классе. Плата излишне велика. Я бы выбрал RTTI.
Ну и последнее. Зачем я заговорил о методе IsHighPriority. У него есть одно очень важное преимущество. Он уменьшает связность. Оба предыдущих варианта требовали от композитора знаний о ВСЕХ классах. Весь смысл композитора как раз в том, чтобы однородно работать со всеми содержащимися в нём объектами, не зная о том, какой же объект на самом деле скрыт под указателем на базу. Если мы вносим в композитор знания о все классах, то все плюсы полиморфизма просто перестают приносить нам плоды. Ведь если композитор знает о всех типах, то мы можем сделать внутри не один вектор для всех объектов, а много векторов на каждый тип. Затем сделать перегрузки для каждого метода Add, а также написать ручной обход всех векторов в каждом функциональном методе-делегате. После этого мы легко избавимся от виртуальных методов. Код, который получится в результате называют в программировании спагетти-кодом (подробнее на вики). Он громоздок, абсолютно нерасширяем, да и просто это "код с душком" (С) Scott Meyers.
И так, знание обо всех типах - это плохо. В то время как метод IsHighPriority позволяет не знать обо всех типах, а лишь разделить их да два множества - типы с высоким приоритетом и не с высоким. Да, мы платим цену лишнего виртуального метода, как и в случае GetID, но мы понимаем, за что платим. Мы уменьшаем связность кода.