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

Я знаком с C++ RTTI и считаю эту концепцию интересной.

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

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

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

Редактировать: Вы найдете ниже код, использующий C++ 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 ;
   }
}

Вы можете начать с публикации своих 1,5 примеров.

Ben Collins 26.10.2008 22:00

Проблема в том, что если я опубликую их, а они будут простейшими примерами (как я полагаю), то большинство людей даже не попытается поделиться своими собственными. Конечно, выложу, если о них никто не подумал.

paercebal 26.10.2008 22:08

:-D ... Нет, нет. Хорошо, первый - это контрактное программирование, когда вы спрашиваете объект, реализует ли он какой-то интерфейс. Если да, то используйте интерфейс. Второй используется, когда у вас сложная иерархия классов и вы не хотите, чтобы ваш базовый класс Object реализовал метод рисования из Shape ...

paercebal 26.10.2008 22:41

1 Я согласен с. 2 звучит как сломанная иерархия.

fizzer 26.10.2008 22:47

"2" - это часть 0,5 ... :-D ... Она не столько сломана, сколько расширена. Если у вас большой фреймворк, состоящий из очень разных объектов, последнее, что вам нужно, - это чтобы все объекты имели одинаковые 3000 методов. Итак, вы устанавливаете «точки расхождения». Объект может быть преобразован в Контейнер и Форму, и [...]

paercebal 26.10.2008 22:50

[...] Итак, Object :: toString существует, Container :: getSize () тоже существует, как и Shape :: draw (). Но не Object :: draw () и не Object :: getSize (). Но каждый раз, производный от Shape, будет рисовать, и каждый тип, производный от Container, будет иметь getSize. В C++ из-за шаблонов [...]

paercebal 26.10.2008 22:53

[...] Этот случай будет появляться не часто, но он может ... Этот пример, хотя и похож на Java, я сначала прочитал на языке программирования C++ Б. Страуструпа. Я реализовал это на примитивном XML-коде графического интерфейса, который так и не был закончен (но все равно был довольно крутым ... :-p ...)

paercebal 26.10.2008 22:55

Эй, "контрактная" часть появилась в stackoverflow.com/questions/238452/… от keysersoze! Я был прав ... :-p

paercebal 26.10.2008 23:07
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
30
8
17 829
10

Ответы 10

Ациклический посетитель (pdf) отлично его использует.

Я согласен. Это снова то, что я назвал выше «контрактным» программированием. +1

paercebal 26.10.2008 23:17

Ссылка больше не действительна. Wayback Machine спешит на помощь: web.archive.org/web/20060313131550/http://www.objectmentor.c‌ om /…

Kunal Tyagi 18.02.2017 12:03

Я не могу сказать, что когда-либо находил применение в реальной жизни, но RTTI упоминается в Эффективный C++ как возможное решение для нескольких методов в C++. Это связано с тем, что диспетчеризация метода выполняется для динамического типа параметра 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);
  }
};

В качестве примечания, есть также упоминание о многометодном имплементации в «Современном дизайне C++» Александреску.

GregC 24.03.2010 20:06

Однажды я работал над симулятором самолета, который имел то, что они (несколько сбивчиво) называли «Базой данных симуляции». Вы можете регистрировать в нем такие переменные, как числа с плавающей запятой, целые числа или строки, и люди могут искать их по имени и извлекать ссылку на них. Вы также можете зарегистрировать модель (объект класса, производного от «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();    
.
.
.

Код довольно псевдо, но я надеюсь, что он передаёт идею. Один разработчик может написать код, который зависит от наличия движка, но пока в нем есть что-то, реализующее интерфейс движка, ему все равно, что это такое. Таким образом, код, который обновляет количество топлива в баках, полностью отделен от всего, кроме функции 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 будет работать нормально.

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

paercebal 26.10.2008 23:05

Прочитав первые строчки Вашего поста, я задумался, почему я не могу вспомнить, что опубликовал некоторые подробности о своем симуляторе самолета. :-D Это должно доказывать, что есть несколько примеров полезных RTTI.

Black 26.10.2008 23:30

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

Как насчет повышения :: любого объекта!

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

Хотя boost: any не использует динамическое приведение, он использует оператор typeid, который работает даже для неполиморфных типов, чтобы убедиться, что приведение правильное. +1

paercebal 27.10.2008 13:56

Вы можете использовать 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));
    }
}

Вероятно, D * d = dynamic_cast <D *> (base); for (i = 0; i! = 1000; ++ i) {d-> Foo (); }. Неправильная идея, так как это не вызывает MoreDerived :: Foo

MSalters 27.10.2008 18:07

Используйте синтаксис d-> D :: Foo (). Я использовал это в добавленном мной примере.

Zan Lynx 27.10.2008 19:16

Интересная микрооптимизация, я должен это запомнить.

Mark Ransom 27.10.2008 19:28

Мне было бы интересно узнать, «почему это происходит быстрее на gcc». Работа, которой удалось избежать в obj->D::key, выполняется с помощью dynamic_cast. И будет ли это хорошо масштабироваться, скажем, для нескольких типов D, E, F и т.д., что означает несколько if (D) / else if (E) / else if (F) / else?

paercebal 11.07.2010 15:03

@paercebal: это сработало, потому что компилятор смог встроить прямой вызов D :: key (i) и более 1000 итераций, экономия от невыполнения вызовов косвенного указателя складывается.

Zan Lynx 12.07.2010 00:09

@Zan Lynx: Ой ... Я был так озадачен тем, что не увидел петли. Конечно, ты прав!

paercebal 12.07.2010 12:25

@Zan Lynx: Как сказал MSalters, что, если это класс DD, который является производным от D и переопределяет key? Это означает, что этот фрагмент кода должен знать точную иерархию классов для работы. Тем не менее, это интересный фрагмент кода. +1.

paercebal 12.07.2010 12:29

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

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

Вы правы: это ужасная комбинация переключателя / RTTI, но тогда вы используете XML, и это решение хорошо для строгого типа, снова того, что было строкой XML <element /> в полноценный объект. +1.

paercebal 27.10.2008 13:32

+1 потому что это именно то, что я хочу сделать, это была лучшая идея, которую я тоже мог придумать, и я ценю знание того, что я что-то не упустил.

T.E.D. 29.05.2014 23:43

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

Иногда приводов 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

вам не нужна «иерархия в форме ромба», чтобы иметь это ...

João Portela 04.01.2010 12:45

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

  1. То же, что уже упоминалось 1800 ИНФОРМАЦИЯ:

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

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

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

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

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

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

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

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

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

Другие вопросы по теме