Начнем закрывать пробелы с небольшой темы.
Итак, первое что нужно вспомнить, это то что у нас есть то, когда проект собирается и то, когда он выполняется. Как это легко мысленно разделить? Например, мы можем на тачке с установленной Linux собрать проект, который будет запускаться на Windows. Тут ни у кого не возникает сомнений, что в какой момент происходит.
Данный пример лишь для того, чтобы было наглядно понятно, что есть этап компиляции/сборки проекта, который может выполняться на одной тачке и есть этап выполнения программы, которая может выполняться совсем на другой тачке. И тут важно не путать, какие операции в какой момент вычисляются.
Сегодня мы поговорим о динамической идентификации типа данных RTTI. Возьмем баянный пример виртуального наследования.
struct Animal { virtual string GetName() const { return "Animal"; } }; struct Cat : public Animal { virtual string GetName() const { return "Cat"; } }; struct Dog : public Animal { virtual string GetName() const { return "Dog"; } }; void PrintName(const Animal& a) { cout << a.GetName() << endl; } int main() { Cat cat; PrintName(cat); return 0; } |
Итак, есть базовый класс животного, и от него наследуется кошка и собака. И есть удобненькая функция PrintName, которая независимо от того собака или кошка ей передается в аргументах, всегда будет выводить правильное имя класса.
В данном примере выведется Cat, а не Animal. Удобно это тем, что каких бы других животных отнаследованных от Animal мы не создавали, функция не будет меняться и при этом будет всегда правильно работать
Но представим, что то пошло не так и мы где то в коде ошиблись и передаем не Cat, а что то явно другое. Как нам узнать какой класс был передан? Самый простой вариант — просто посмотреть тип typeid(a).name().
void PrintName(const Animal& a) { cout << a.GetName() << endl; cout << typeid(a).name() << endl; } |
typeid() это первая часть RTTI, т.е. тип данных будет вычисляться во время работы приложения. Минусом этого подхода являются дополнительные расходы на получение типа.
Вторая часть RTTI это dynamic_cast. В случае преобразования из типа Cat в тип Animal нам не нужно производить никаких дополнительных телодвижений. Можно создать ссылку Animal и присвоить ей объект cat без всяких проблем. Результат работы не изменится.
int main() { Cat cat; Animal &a = cat; PrintName(a); return 0; } |
Такое преобразование называется upcast. Но в обратную сторону, так же легко это сделать не получится. Для downcast используется dynamic_cast. Например, доработаем функцию PrintName. В примере ниже для Cat дополнительный текст будет выводиться, для Dog нет.
#include <iostream> #include <typeinfo> using namespace std; struct Animal { virtual string GetName() const { return "Animal"; } }; struct Cat : public Animal { virtual string GetName() const { return "Cat"; } }; struct Dog : public Animal { virtual string GetName() const { return "Dog"; } }; void PrintName(Animal& a) { cout << a.GetName() << endl; if(Cat* c = dynamic_cast<Cat*>(&a)) { cout << "Meow meow mf" << endl; } } int main() { Cat a; Dog d; PrintName(a); PrintName(d); return 0; } |
Стоит отметить, что dynamic_cast работает именно в рантайме и проверяет на соответствие типов во время работы программы. Лично мне не доводилось пользоваться dynamic_cast, поэтому сложно придумать какой то жизненный пример, но если таковой появится то обязательно дополню статью
Добавить комментарий