Начнем закрывать пробелы с небольшой темы.

Итак, первое что нужно вспомнить, это то что у нас есть то, когда проект собирается и то, когда он выполняется. Как это легко мысленно разделить? Например, мы можем на тачке с установленной 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, поэтому сложно придумать какой то жизненный пример, но если таковой появится то обязательно дополню статью

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Последние комментарии
  • Загрузка...
Счетчик
Яндекс.Метрика