Начнем закрывать пробелы с небольшой темы.
Итак, первое что нужно вспомнить, это то что у нас есть то, когда проект собирается и то, когда он выполняется. Как это легко мысленно разделить? Например, мы можем на тачке с установленной 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
#include
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(&a))
{
cout << "Meow meow mf" << endl;
}
}
int main()
{
Cat a;
Dog d;
PrintName(a);
PrintName(d);
return 0;
}
Стоит отметить, что dynamic_cast работает именно в рантайме и проверяет на соответствие типов во время работы программы. Лично мне не доводилось пользоваться dynamic_cast, поэтому сложно придумать какой то жизненный пример, но если таковой появится то обязательно дополню статью


Добавить комментарий