Есть ли более быстрый способ определения типа объекта во время выполнения, чем использование dynamic_cast? - PullRequest
6 голосов
/ 16 июня 2009

У меня есть иерархия типов - GenericClass и ряд производных классов, в том числе InterestingDerivedClass, GenericClass полиморфный. Там есть интерфейс

interface ICallback {
    virtual void DoStuff( GenericClass* ) = 0;
};

, что мне нужно реализовать. Затем я хочу обнаружить случай, когда указатель GenericClass *, переданный в ICallback :: DoStuff (), действительно является указателем на InterestingDerivedClass:

class CallbackImpl : public ICallback {
    void DoStuff( GenericClass* param ) {
        if( dynamic_cast<InterestingDerivedClass*>( param ) != 0 ) {
            return; //nothing to do here
        }
        //do generic stuff
    }
}

GenericClass и производные классы находятся вне моего контроля, я только контролирую CallbackImpl.

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

Поскольку мне действительно не нужен указатель на производный класс, существует ли более быстрый способ определения типа объекта во время выполнения только с использованием RTTI? Может быть, какой-то специфический для реализации метод, который только проверяет отношение «является», но не извлекает указатель?

Ответы [ 8 ]

12 голосов
/ 16 июня 2009

Я всегда смотрю на использование dynamic_cast как запах кода. Вы можете заменить его при любых обстоятельствах полиморфным поведением и улучшить качество своего кода. В вашем примере я бы сделал что-то вроде этого:

class GenericClass
{
  virtual void DoStuff()
  {
    // do interesting stuff here
  }
};

class InterestingDerivedClass : public GenericClass
{
  void DoStuff()
  {
    // do nothing
  }
};

class CallbackImpl : public ICallback {
    void DoStuff( GenericClass* param ) {
        param->DoStuff();
    }
}

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

5 голосов
/ 16 июня 2009

Как уже говорили другие, использование виртуальной функции является хорошей практикой. Есть еще одна причина его использования, которая не применима в вашем случае, поскольку вы не можете добавить VF, но все же стоит упомянуть, я думаю, - это может быть намного быстрее, чем использование динамического приведения. В некоторых (не очень строгих) тестах, которые я проводил с g ++, виртуальная функция превзошла dynamic_cast в 4 раза.

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

2 голосов
/ 16 июня 2009

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

Но нет, единственное общее решение, о котором я знаю, это dynamic_Cast. typeid соответствует только наиболее производному типу, который может работать в вашем случае.

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

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

1 голос
/ 16 июня 2009

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

Пример реализации:

// fast dynamic cast
//! Fast dynamic cast declaration
/*!
Place USE_CASTING to class that should be recnognized by dynamic casting.
Do not forget do use DEFINE_CASTING near class definition.
*\note Function dyn_cast is fast and robust when used correctly.
Each class that should be used as target for dyn_cast
must use USE_CASTING and DEFINE_CASTING macros.\n
Forgetting to do so may lead to incorrect program execution,
because class may be sharing _classId with its parent and IsClassId
will return true for both parent and derived class, making impossible'
to distinguish between them.
*/
#define USE_CASTING(baseType) \
  public: \
  static int _classId; \
  virtual size_t dyn_sizeof() const {return sizeof(*this);} \
  bool IsClassId( const int *t ) const \
  { \
    if( &_classId==t ) return true; \
    return baseType::IsClassId(t); \
  }

//! Fast dynamic cast root declaration
/*!
Place USE_CASTING_ROOT to class that should act as
root of dynamic casting hierarchy
*/

#define USE_CASTING_ROOT \
  public: \
  static int _classId; \
  virtual size_t dyn_sizeof() const {return sizeof(*this);} \
  virtual bool IsClassId( const int *t ) const { return ( &_classId==t ); }

//! Fast dynamic cast definition
#define DEFINE_CASTING(Type) \
  int Type::_classId;

template <class To,class From>
To *dyn_cast( From *from )
{
  if( !from ) return NULL;
  if( from->IsClassId(&To::_classId) )
  {
    assert(dynamic_cast<To *>(from));
    return static_cast<To *>(from);
  }
  return NULL;
}

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

Еще одно замечание: если вы говорите, что рассматриваемые классы находятся вне вашего контроля, это решение вам не поможет, так как требует, чтобы вы изменили классы (не так много, просто добавьте несколько строк, но вам нужно изменить их). Если это действительно так, вам нужно использовать то, что предлагает язык, то есть dynamic_cast и typeinfo.

1 голос
/ 16 июня 2009

В вашем конкретном случае использования ответ заключается в использовании виртуальных функций.

Однако существуют ситуации, когда вам нужно динамически снижать рейтинг. Есть несколько методов, чтобы сделать эту операцию быстрее (или намного быстрее, в зависимости от того, насколько умный ваш компилятор реализует dynamic_cast), в частности, если вы ограничиваете себя одним наследованием. Основная идея заключается в том, что если вы каким-то образом знаете точный тип, static_cast будет намного быстрее:

f(A* pA)
{
  if (isInstanceOfB(pA))
  {
    B* pB = static_cast<B*>(pA);
    // do B stuff...
  }
}

Конечно, теперь проблема заключается в быстрой реализации isInstanceOfB ().

См., Например, boost :: type_traits .

1 голос
/ 16 июня 2009

Во-первых, не оптимизируйте преждевременно. Во-вторых, если вы делаете запрос к объекту для конкретной реализации внутри, вероятно, что-то не так с вашим дизайном (подумайте, двойная диспетчеризация).

Что касается первоначального вопроса, то введение функции GetRuntimeType() в ICallback будет весьма кстати: о том, как это можно сделать, см. В MFC.

1 голос
/ 16 июня 2009

Сравнение type_info будет быстрее? (вызов typeid по параметру param)

0 голосов
/ 16 июня 2009

Можете ли вы использовать http://www.boost.org/doc/libs/1_39_0/boost/type_traits/is_convertible.hpp и проверить производительность процессора?

Вы также можете проверить реализацию ..

Ссылка: http://www.boost.org/doc/libs/1_39_0/libs/type_traits/doc/html/boost_typetraits/reference/is_convertible.html

...