Подтвердить что ты не робот

С++ RTTI Жизнеспособные примеры

Я знаком с С++ RTTI и нашел интересную концепцию.

Тем не менее существует много способов злоупотребления этим, чем использовать его правильно (на ум приходит ошибка RTTI-switch). Как разработчик, я нашел (и использовал) только два жизнеспособных для него использования (точнее, один с половиной).

Не могли бы вы поделиться некоторыми из способов, которыми RTTI является жизнеспособным решением проблемы, включая код кода/псевдокода?

Примечание. Цель состоит в том, чтобы иметь хранилище жизнеспособных примеров, которые младший разработчик может консультировать, критиковать и учиться.

Изменить: Ниже вы найдете код с использованием С++ RTTI

// A has a virtual destructor (i.e. is polymorphic)
// B has a virtual destructor (i.e. is polymorphic)
// B does (or does not ... pick your poison) inherits from A

void doSomething(A * a)
{
   // typeid()::name() returns the "name" of the object (not portable)
   std::cout << "a is [" << typeid(*a).name() << "]"<< std::endl ;

   // the dynamic_cast of a pointer to another will return NULL is
   // the conversion is not possible
   if(B * b = dynamic_cast<B *>(a))
   {
      std::cout << "a is b" << std::endl ;
   }
   else
   {
      std::cout << "a is NOT b" << std::endl ;
   }
}
4b9b3361

Ответ 1

Acyclic Visitor (pdf) - это большое его использование.

Ответ 2

Как насчет boost:: any object!

Это в основном использует информацию RTTI для хранения любого объекта и получения этого объекта boost:: any_cast < > .

Ответ 3

Вы можете использовать RTTI с dynamic_cast, чтобы получить указатель на производный класс, чтобы использовать его для вызова быстрого специализированного алгоритма типа. И вместо того, чтобы использовать виртуальные методы через базовый класс, он будет выполнять прямые и встроенные вызовы.

Это ускорилось для меня, используя GCC. Visual Studio, похоже, не работает, возможно, имеет более медленный поиск dynamic_cast.

Пример:

D* obj = dynamic_cast<D*>(base);
if (obj) {
    for(unsigned i=0; i<1000; ++i)
        f(obj->D::key(i));
    }
} else {
    for(unsigned i=0; i<1000; ++i)
        f(base->key(i));
    }
}

Ответ 4

Я не могу сказать, что я когда-либо находил применение в реальной жизни, но RTTI упоминается в Эффективном С++ в качестве возможного решения для нескольких -методы в С++. Это происходит потому, что отправка метода выполняется по динамическому типу параметра this, но статический тип аргументов.

class base
{
  void foo(base *b) = 0; // dynamic on the parameter type as well
};

class B : public base {...}
class B1 : public B {...}
class B2 : public B {...}

class A : public base
{
  void foo(base *b)
  {
    if (B1 *b1=dynamic_cast<B1*>(b))
      doFoo(b1);
    else if (B2 *b2=dynamic_cast<B2*>(b))
      doFoo(b2);
  }
};

Ответ 5

Я работал над симуляцией самолета один раз, и у них было то, что они (несколько смутно) называли "Базой моделирования". Вы можете регистрировать переменные типа float или ints или строки в нем, и люди могут искать их по имени и вытаскивать ссылку на них. Вы также можете зарегистрировать модель (объект класса, произошедшего от "SimModel" ). То, как я использовал RTTI, заключалось в том, чтобы вы могли искать модели, реализующие данный интерфейс:

SimModel* SimDatabase::FindModel<type*>(char* name="")
{
   foreach(SimModel* mo in ModelList)
   if(name == "" || mo->name eq name)
   {
       if(dynamic_cast<type*>mo != NULL)
       {
           return dynamic_cast<type*>mo;
       }
   }
   return NULL;
}

Базовый класс SimModel:

class public SimModel
{
    public:
        void RunModel()=0;
};

Пример интерфейса может быть "EngineModel":

class EngineModelInterface : public SimModel
{
    public:
        float RPM()=0;
        float FuelFlow()=0;
        void SetThrottle(float setting)=0; 
};

Теперь, чтобы создать движок Lycoming и Continental:

class LycomingIO540 : public EngineModelInterface 
{
    public:
        float RPM()
        {
            return rpm;
        }
        float FuelFlow()
        {
            return throttleSetting * 10.0;
        }
        void SetThrottle(float setting) 
        {
            throttleSetting = setting
        }
        void RunModel() // from SimModel base class
        {
            if(throttleSetting > 0.5)
                rpm += 1;
            else
                rpm -= 1;
        }
    private:
        float rpm, throttleSetting;
};
class Continental350: public EngineModelInterface 
{
    public:
        float RPM()
        {
            return rand();
        }
        float FuelFlow()
        {
            return rand;
        }
        void SetThrottle(float setting) 
        {
        }
        void RunModel() // from SimModel base class
        {
        }
};

Теперь вот какой код, где кто-то хочет движок:

.
.
EngineModelInterface * eng = simDB.FindModel<EngineModelInterface *>();
.
.
fuel = fuel - deltaTime * eng->FuelFlow();    
.
.
.

Код довольно псевдо, но я надеюсь, что он получит эту идею. Один разработчик может писать код, который зависит от наличия Engine, но пока он имеет что-то, что реализует интерфейс движка, ему все равно, что это такое. Таким образом, код, который обновляет количество топлива в резервуарах, полностью отделен от всего, кроме функции FindModel < > (), и чистого виртуального интерфейса EngineModel, который он заинтересован в использовании. Кто-то через год может создать новую модель двигателя, зарегистрировать ее с помощью SimulationDatabase, а парень, выше которого обновляет топливо, начнет использовать его автоматически. Я действительно сделал это, чтобы вы могли загружать новые модели в виде плагинов (DLL) во время выполнения, и как только они зарегистрированы в SimulationDatabase, их можно найти с помощью FindModel < > (), хотя код, который их ищет, был скомпилирован и встроенный в DLL за несколько месяцев до появления новой DLL. Вы также можете добавить новые интерфейсы, которые производятся от SimModel, с чем-то, что реализует их в одной DLL, что-то, что ищет их в другой DLL, и как только вы загружаете обе библиотеки DLL, можно сделать FindModel < > (), чтобы получить модель в другой. Даже несмотря на то, что сам интерфейс даже не существовал при создании основного приложения.

В свою очередь, RTTI не всегда работает через границы DLL. Так как я использовал Qt, я использовал qobject_cast вместо dynamic_cast. Каждый класс должен был наследовать от QObject (и получить moc'd), но метаданные qobject всегда были доступны. Если вы не заботитесь о DLL или используете инструментальную цепочку, где RTTI работает через границы DLL (сопоставляйте сравнения, основанные на сравнении строк вместо хэшей или что-то еще), то все вышеперечисленное с dynamic_cast будет работать нормально.

Ответ 6

Я использую его в дереве классов, который сериализуется в XML файл. При де-сериализации класс парсера возвращает указатель на базовый класс, который имеет перечисление для типа подкласса (поскольку вы не знаете, какой тип он есть, пока вы его не разобрали). Если код, использующий объект, должен ссылаться на определенные элементы подкласса, он включает значение перечисления и dynamic_cast в подкласс (который был создан парсером). Таким образом, код может проверяться, чтобы убедиться, что у парсера не было ошибки и несоответствия между значением перечисления и возвращаемым типом экземпляра класса. Виртуальных функций также недостаточно, потому что у вас могут быть конкретные данные подкласса, которые вам нужно получить.

Это только один пример того, где RTTI может быть полезным; это, возможно, не самый элегантный способ решения проблемы, но использование RTTI делает приложение более надежным при использовании этого шаблона.

Ответ 7

Иногда static_cast и приведения в стиле C недостаточно, и вам нужно dynamic_cast, пример этого - когда у вас есть страшный ромбовидная иерархия (изображение из Википедии).

diamond inheritance

struct top {
};

struct left : top { 
    int i;
    left() : i(42) {}
};

struct right : top {
    std::string name;
    right() : name("plonk") { }
};

struct bottom : left, right {
};

bottom b;
left* p = &b;

//right* r = static_cast<right*>(p); // Compilation error!
//right* r = (right*)p;              // Gives bad pointer silently 
right* r = dynamic_cast<right*>(p);  // OK

Ответ 8

Используйте случаи, которые у меня есть в моих проектах (если вы знаете лучшее решение для конкретных случаев, прокомментируйте):

  • То же самое, что и 1800 INFORMATION, уже упоминалось:

    Вам понадобится dynamic_cast для реализации operator== или operator< для производных классов. Или, по крайней мере, я не знаю другого пути.

  • Если вы хотите реализовать что-то вроде boost::any или другого контейнера вариантов.

  • В одной игре в классе Client, который имел std::set<Player*> (возможные экземпляры NetPlayer и LocalPlayer) (который мог иметь не более одного LocalPlayer), мне нужна была функция LocalPlayer* Client::localPlayer(). Эта функция очень редко используется, поэтому я хотел избегать помех Client с дополнительной локальной переменной-членом и всем дополнительным кодом для обработки этого.

  • У меня есть некоторый абстрактный класс Variable с несколькими реализациями. Все зарегистрированные Variable находятся в некотором std::set<Variable*> vars. И есть несколько встроенных vars типа BuiltinVar, которые сохраняются в std::vector<BuiltinVar> builtins. В некоторых случаях у меня есть Variable* и нужно проверить, является ли это BuiltinVar* и внутри builtins. Я мог бы сделать это через некоторую проверку диапазона памяти или через dynamic_cast (я могу быть уверен, что в любом случае все экземпляры BuiltinVar находятся в этом векторе).

  • У меня есть сетка с сеткой GameObject, и мне нужно проверить, есть ли в одной сетке объект Player (специализированный GameObject). Я мог бы иметь функцию bool GameObject::isPlayer(), которая всегда возвращает false, за исключением Player, или я мог бы использовать RTTI. Существует еще много таких примеров, когда люди часто реализуют такие функции, как Object::isOfTypeXY(), и из-за этого базовый класс сильно загромождает.

    Это также случается и для других очень специальных функций, таких как Object::checkScore_doThisActionOnlyIfIAmAPlayer(). Существует некоторый здравый смысл, необходимый для принятия решения, когда на самом деле имеет смысл иметь такую ​​функцию в базовом классе, а когда нет.

  • Иногда я использую его для проверок безопасности или проверки выполнения.

  • Иногда мне нужно хранить указатель на некоторые данные в некотором поле данных некоторой библиотеки C (например, SDL или что нет), и я получаю его где-то еще позже или в качестве обратного вызова. Я делаю dynamic_cast здесь, чтобы убедиться, что получаю то, что ожидаю.

  • У меня есть класс TaskManager, который выполняет некоторую очередь Task s. Для некоторых Task s, когда я добавляю их в список, я хочу удалить из очереди другой Task того же типа.

Ответ 9

Я использовал RTTI, когда несколько лет назад я работал над Qt на основе canvas. Было удобно, когда вы делаете хит-тесты на объектах, чтобы использовать RTTI, чтобы определить, что я собираюсь делать с формой, которую я бы нажал. Но я не использовал его иначе в производственном коде.

Ответ 10

Я использую его с Dynamic Double Dispatch и Templates. В принципе, это дает возможность наблюдать/слушать только интересные части объекта.