RTT — C ++ быстрая динамическая проверка типа / подтипа

Как следует из названия, я ищу быстрый способ проверки типов во время выполнения. Чтобы проиллюстрировать мою проблему, представьте, что у вас есть иерархия классов, подобная следующей:

      Base
/     \
A       D
/ \     / \
C   B   F   E
\ /
G

Моя программа хранит все экземпляры любого класса в одном списке как Base_ptr, потому что все эти классы имеют общие задачи. Теперь в какой-то момент некоторые производные классы должны будут знать о существовании экземпляра другого класса. Пока все хорошо, я знаю о dynamic_cast и операторе typeid (), но оба имеют некоторые недостатки мэра:

  1. dynamic_cast потребляет много времени на обработку, если типы несовместимы (например, попытайтесь привести экземпляры E к C)
  2. typeid () не работает в случаях isTypeOrSubtype, например вам нужны все экземпляры D или производные от D (так же Es, Fs и Gs)

Идеальным решением было бы какое-то «isTypeOrSubtype» -тестирование и только приведение, если этот тест завершится успешно. У меня есть собственный подход с некоторыми определениями макросов и предварительно вычисленными хэшами имен классов, но он очень уродливый и трудно обслуживаемый. Поэтому я ищу более чистый и быстрый способ динамической проверки типов и подтипов, который может проверять намного больше, чем 20 миллионов раз в секунду.

0

Решение

Я написал ответ на свой вопрос, так как это другой подход, позволяющий избежать RTTI, но нет реального ответа на быстрый способ динамической проверки типа / подтипа.
Это все еще не чистое решение, но лучшее, что я мог придумать до сих пор.

Если у каждого класса в этой иерархии есть следующие характеристики, я могу пропустить большую часть RTTI.

  1. каждый класс должен иметь закрытого члена: static SecureVector<[class]*> s_Instances; где SecureVector<T> является потокобезопасным вектором.
  2. в конце каждого конструктора, s_Instances.push_back(this); должен вызываться, чтобы отслеживать вновь созданный экземпляр этого класса
  3. в начале деструктора, s_Instances.erase(this); должен быть вызван, чтобы удалить эту ссылку экземпляры
  4. каждый класс должен иметь публичную функцию: static const SecureVector<[class]*>& Instances() { return s_Instances; } получить неизменяемый вектор, содержащий все экземпляры этого или любого производного класса

При каждом вызове конструктора экземпляр добавляет себя в свой список экземпляров. Когда производные классы вызывают свой супер-конструктор, супер-класс добавляет себя в соответствующий список экземпляров.
Например. если бы я случайно создал 100 экземпляров в вышеприведенной иерархии, всегда было бы 100 записей в моем Base учебный класс Instances() вектор.

В коде это будет выглядеть так:

class Base
{
static SecureVector<Base*> s_Instances; // 1. condition

public:

Base()
{
s_Instances.push_back(this);    // 2. condition
}

~Base()
{
s_Instances.erase(this);    // 3. condition
}

static const SecureVector<Base*>& Instances() { return s_Instances; } // 4. condition
};

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

0

Другие решения

Некоторое время назад я использовал что-то вроде этого:

// the actual type is irrelevant, const char*, int, ...
// but const char* is great for debugging, when it contains the actual class name
typedef const char* TypeId;

class Base {

// actually the type id is not the value, but its memory location !
// the value is irrelevant (but its a good idea to set it to the class name)
static TypeId s_myTypeId;

public:

static TypeId* getClassType()          { return &s_myTypeId; }
virtual TypeId* getInstanceType()      { return &s_myTypeId; }

static TypeId* getClassBaseType()      { return NULL; }
virtual TypeId* getInstanceBaseType()  { return NULL; }

virtual bool isType( TypeId* type )            { return type==getInstanceType(); }
virtual bool isTypeOrSubType( TypeId* type )   { return isType(type); }

};template< class MyBase >
class TBase : public MyBase {

// actually the type id is not the value, but its memory location !
// the value is irrelevant (but its a good idea to set it to the class name)
static TypeId s_myTypeId;

public:

static TypeId* getClassType()          { return &s_myTypeId; }
virtual TypeId* getInstanceType()      { return &s_myTypeId; }

static TypeId* getClassBaseType()      { return MyBase::getClassType(); }
virtual TypeId* getInstanceBaseType()  { return MyBase::getInstanceType(); }

virtual bool isType( TypeId* type )            { return type==getInstanceType(); }
virtual bool isTypeOrSubType( TypeId* type )   { return isType(type) || MyBase::isTypeOrSubType(type); }

};

// by deriving from TBase<Base>, a new instantiation of s_myTypeId was created,
// so the class now has its very own unique type id,
// and it inherited all the type resolution magic
class A : public TBase<Base> {
};

// NOTE: class B must not derive directly from A, but from TBase<A>
// imagine a hidden class between B and A,
// actually B inherits from the TBase<A> instantiation, which in turn inherits from A
class B : public TBase<A> {
};

// you will also need to instantiate the static members
// hereby the memory location will be reserved,
// and on execution that memory location becomes the unique type id
#define IMPLEMENT_RTTI(CL) TypeId CL::s_myTypeId = STRFY(CL)

// one per class per source file:
IMPLEMENT_RTTI(Base);
IMPLEMENT_RTTI(A);
IMPLEMENT_RTTI(B);

// example usage:
A a;
B b;

b.getInstanceType()==B::getClassType();     // TRUE
b.getInstanceBaseType()==A::getClassType(); // TRUE
B::getClassBaseType()==A::getClassType();   // TRUE

b.isType( B::getClassType() );              // TRUE
b.isType( A::getClassType() );              // FALSE

b.isTypeOrSubType( B::getClassType() );     // TRUE
b.isTypeOrSubType( A::getClassType() );     // TRUE
b.isTypeOrSubType( Base::getClassType() );  // TRUE

Это безопасно, быстро и просто в использовании. Вы просто должны соблюдать два правила:

  • не наследовать напрямую от class X, но наследовать от TBase<X>,
  • и добавить IMPLEMENT_RTTI(Me) в исходный файл.

Есть один недостаток: он еще не поддерживает множественное наследование. Но это было бы возможно с несколькими изменениями.

И, вероятно, TypeId тип должен быть составлен как typedef const char* TypeLoc а также typedef TypeLoc* TypeId, Может быть, просто вопрос вкуса.

0

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

class Base {
//...
virtual A * is_A () { return 0; }
virtual B * is_B () { return 0; }
//...
template <typename MAYBE_DERIVED>
MAYBE_DERIVED * isTypeOrSubtype () {
//...dispatch to template specialization to call is_X()
}
};

//...
class A : virtual public Base {
//...
A * is_A () { return this; }
};

На IDEONE предложенная техника в 20-50 раз быстрее, чем используя динамическое приведение.1 Реализация использует макросы, чтобы разрешить добавление нового класса в одном месте, и после этого соответствующие расширения базового класса выполняются автоматически.

(1) — Я изначально разогнал его примерно в 100 раз быстрее, но это было без isTypeOrSubtype() метод-обёртка, который я добавил для симуляции нужного интерфейса.

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

class Base {
std::map<const char *, void *> children_;
//...
template <typename MAYBE_DERIVED>
MAYBE_DERIVED * isTypeOrSubtype () {
auto x = children_.find(typeid(MAYBE_DERIVED).name());
return ((x != children_.end())
? static_cast<MAYBE_DERIVED *>(x->second)
: 0);
}
};

//...
class A : virtual public Base {
//...
A () { children_[typeid(A).name()] = this; }
//...
};

На IDEONE это второе предложение в 10-30 раз быстрее используя динамическое приведение. Я не думаю, что IDEONE компилируется с оптимизацией, поэтому я ожидаю, что время будет ближе к первому предложению о сборке. Механизм как реализовано использует typeid(...).name() как ключ к карте.2

(2) — Это предполагает, что typeid(...).name() возвращает что-то похожее на строковый литерал и всегда возвращает один и тот же указатель строки при работе с одним и тем же типом. Если ваша система не ведет себя таким образом, вы можете изменить map взять std::string вместо этого, но производительность будет ухудшена.

0

dynamic_cast будет работать чудесно для этого!

Base *instance = //get the pointer from your collection;
A* ap = dynamic_cast<A*>(instance);
D* dp = dynamic_cast<D*>(instance);

if(ap) {
//instance is an A or a subclass of A
}
if(dp) {
//instance is a D or a subclass of D
}

Это будет работать и для более конкретных проверок. Таким образом, вы можете проверить любой тип, который вы хотите.

-1
По вопросам рекламы [email protected]