C ++: эмуляция RTTI - PullRequest
       25

C ++: эмуляция RTTI

10 голосов
/ 04 декабря 2010

У меня есть иерархия классов, как эта:

class A        { }                            //
class AA  : A  { }                            //          A
class AAA : AA { }                            //        /   \
class AAB : AA { }                            //      AA     AB
class AB  : A  { }                            //     / \     / \
class ABA : AB { }                            //   AAA AAB ABA ABB
class ABB : AB { }                            //

Я хотел бы эмулировать RTTI (без его использования, конечно) для этой иерархии, таким образом, что, учитывая указатель/ ссылка на A, я могу узнать его фактический тип (аналогично тому, что делает typeid) как целое число, идентифицирующее класс.

Более того, я хотел бы, чтобы набор целых чисел идентифицировал мои типыявляется смежным и идет от 0 до N-1 (от 0 до 6 в моем примере):

class A        { virtual int t(){return 0;} } //
class AA  : A  { virtual int t(){return 1;} } //            A(0)
class AAA : AA { virtual int t(){return 2;} } //          /      \
class AAB : AA { virtual int t(){return 3;} } //      AA(1)       AB(4)
class AB  : A  { virtual int t(){return 4;} } //     /   \        /    \
class ABA : AB { virtual int t(){return 5;} } // AAA(2) AAB(3) ABA(5) ABB(6)
class ABB : AB { virtual int t(){return 6;} } //

(порядок не имеет значения: A::t может вернуть 3 и AAB::t 0,например.


Можно ли разрешить компилятору присваивать индексы моим классам?

Я думаю, что CRTP может помочь мне, что-то вроде:

class X : A, AssignFirstAvailableIndex< X > { }

но я недостаточно хорош с шаблонами. Как я могу реализовать этот шаблонный класс AssignFirstAvailableIndex?

(конечно, компилятор может видеть все классы во время компиляции)

Ответы [ 4 ]

3 голосов
/ 05 декабря 2010

Существует стандартный метод для реализации того, что вам нужно. Стандартные языковые аспекты используют его для идентификации себя. Рассмотрим проверку стандартного заголовка "locale".

class Base {
  public:
  // Being constructed contains a new unique identifier
  class Id {
    // An id factory returns a sequence of nonnegative ints
    static int allocate() {
      static int total = 0;
      return total++;
    }
    int _local;
    public:
    Id(): _local(allocate()) {}
    int get() const {return _local;}
  };
  //Child classes should make this function return an id generated by Base::Id constructed as static member.
  virtual int id() const = 0;
};

class Child1{
  public:
  static const Base::Id _id; 
  virtual int id() { return _id.get(); }
};

class Child2 {
  public:
  static const Base::Id _id; 
  virtual int id() { return _id.get(); }
};

class Child3 {
  public:
  static const Base::Id _id; 
  virtual int id() { return _id.get(); }
};

Статические члены могут быть инициализированы в файлах реализации или иметь шаблоны для разрешения создания экземпляров непосредственно из заголовков или могут быть реорганизованы для отложенной инициализации.

0 голосов
/ 26 января 2012

Я успешно использовал метод, описанный здесь: http://www.flipcode.com/archives/Run_Time_Type_Information.shtml. В основном каждый класс, для которого требуется RTTI, имеет статическую функцию, которая возвращает свой тип rtti.Преимущество использования структуры для типа заключается в том, что вы можете добавлять функции в вашу структуру rtti.

Я изменил этот подход, чтобы разрешить такие вещи, как component->IsOfType(CollisionComponent::GetClass()).

Я также расширил структуру RTTIчтобы предоставить имя класса, и теперь я могу вызвать component->IsOfType("CollisionComponent") Без включения CollisionComponent.h.

Этот подход очень удобен в сочетании с это динамическим созданием класса.Я могу создавать и идентифицировать классы C ++ в сценариях и создавать большое количество различных компонентов, которые загружаются только тогда, когда ОС / оборудование поддерживает необходимые типы.Я также могу перенастроить типы загружаемых компонентов в зависимости от версии сервера.Например, сервер может разрешить использование "CollisionComponent_V2_0", в то время как другой будет принудительно использовать "CollisionComponent_Proxy_V2"

0 голосов
/ 04 декабря 2010

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

Если вам действительно нужны целочисленные идентификаторы, вероятно, их проще всего настроить во время выполнения, чем пытаться слишком усердно.

Во-первых, создайте объект, представляющий тип, и используйтетипичный подход hand-made-RTTI: каждый класс должен иметь статический экземпляр этого объекта, а его виртуальная функция get-type-info возвращает указатель на этот объект.Таким образом, в каждом классе будет немного кода, например:

static TypeInfo ms_type_info;
virtual const TypeInfo *GetTypeInfo() const {
    return &ms_type_info;
}

И вы определяете информацию о типе, помещая в раздел <<whatever you info you want>> любую информацию, которую TypeInfo сохраняет всделайте его лучше, чем RTTI компилятора;

TypeInfo WhateverClass::ms_type_info(<<whatever info you want>>);

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

Сама структура TypeInfo будет выглядеть примерно так:

struct TypeInfo {
    int type_index;
    TypeInfo *next;
    TypeInfo(<<whatever>>) {<<see below>>}
};

Если бы читатель предпочел функции get и set, он мог бы иметьте.

Объект TypeInfo должен быть статическим по отношению к классу, а не к функции, поскольку целью является создание списка всех TypeInfos.У вас есть два варианта здесь.В автоматическом режиме каждый TypeInfo в конструкторе, который оставлен пустым, добавляется в какой-то глобальный связанный список;другая - иметь большую функцию, которая добавляет нужные в глобальный список вручную.

Затем при запуске пробежите объекты TypeInfo и присвойте каждому индекс.Примерно так и поступило бы, предполагая, что есть TypeInfo *g_first_type_info, который указывает на первую информацию о типе в списке:

int next_type_index=0;
for(TypeInfo *ti=g_first_type_info;ti;ti=ti->next)
    ti->type_index=next_type_index++;

Теперь, когда вам нужен ваш целочисленный идентификатор, вы можете легко получить его:

object->GetTypeInfo()->type_index;

Вы можете легко реализовать t сейчас:

virtual int t() const {
    return ms_type_info.type_index;
}

Полное раскрытие проблем, о которых я могу думать:

  • Типиндексы не устанавливаются во время компиляции, поэтому, если вы собираете массивы, вам нужно создавать их во время выполнения.Это может быть пустой тратой кода, если ваши массивы иначе были бы постоянными времени компиляции - однако для компиляторов, которые не поддерживают синтаксис инициализации массива в стиле C99, это может иногда сделать код более читабельным.

  • Если вам нравится делать все в глобальных конструкторах до запуска main, вы можете открепиться, поскольку объекты TypeInfo являются просто обычными глобальными объектами и (в любом случае, в этой ситуации) все равно не готовы для использованияпока не будут назначены индексы типов.

  • Линкеры имеют тенденцию отбрасывать глобальные объекты, которые, кажется, не используются, поэтому объекты информации типа в статических библиотеках могут никогда не добавить себя всписок.Таким образом, вы должны иметь в виду, что хотя автоматический подход к регистрации работает хорошо, когда он не работает, он не работает, так что в конечном итоге вам, в любом случае, придется регистрировать вещи вручную.

0 голосов
/ 04 декабря 2010

Может как то так?(синтаксис, вероятно, неверен повсюду, но вы поняли)

class A {
  virtual int t() { return 0; }
}

template <bool AB>
class AX<AB>: A {
  virtual int t() { return AB ? 1 : 2; }
};

template <bool AB2>
template <bool AB>
class AXX<AB2>: AX<AB> {
  virtual int t() { return AX<AB>::t() * 2 + (AB2 ? 1 : 2); }
}

... А если серьезно, пожалуйста просто используйте RTTI.

...