Насколько дорого стоит RTTI?

Я понимаю, что использование RTTI снижает ресурс, но насколько он велик? Куда бы я ни посмотрел, везде написано, что «RTTI - дорогое удовольствие», но ни один из них на самом деле не дает никаких тестов или количественных данных, касающихся памяти, времени процессора или скорости.

Итак, насколько дорого стоит RTTI? Я мог бы использовать его во встроенной системе, где у меня всего 4 МБ ОЗУ, поэтому каждый бит на счету.

Изменить: Согласно ответу С. Лотта, было бы лучше, если бы я включил что я на самом деле делаю. Я использую класс для передачи данных разной длины и который может выполнять разные действия, поэтому было бы сложно сделать это с помощью только виртуальные функции. Кажется, что использование нескольких dynamic_casts может решить эту проблему, позволяя различным производным классам проходить через разные уровни, но при этом позволяя им действовать совершенно по-разному.

Насколько я понимаю, dynamic_cast использует RTTI, поэтому мне было интересно, насколько возможно его использовать в ограниченной системе.


person Cristián Romo    schedule 23.02.2009    source источник
comment
Следуя вашему редактированию - очень часто, когда я выполняю несколько динамических приведений, я понимаю, что использование шаблона Visitor снова исправляет ситуацию. Может ли это сработать для вас?   -  person philsquared    schedule 23.12.2009
comment
Я скажу так - я только начал использовать dynamic_cast в C ++, и теперь в 9 из 10 раз, когда я нарушаю программу с помощью отладчика, она ломается внутри внутренней функции динамического приведения. Это чертовски медленно.   -  person user541686    schedule 10.09.2012
comment
Между прочим, RTTI = информация о типе времени выполнения.   -  person Noumenon    schedule 18.11.2014


Ответы (11)


Независимо от компилятора, вы всегда можете сэкономить на времени выполнения, если можете себе позволить

if (typeid(a) == typeid(b)) {
  B* ba = static_cast<B*>(&a);
  etc;
}

вместо

B* ba = dynamic_cast<B*>(&a);
if (ba) {
  etc;
}

В первом случае используется только одно сравнение std::type_info; последнее обязательно включает обход дерева наследования плюс сравнения.

В прошлом ... как все говорят, использование ресурсов зависит от конкретной реализации.

Я согласен со всеми остальными комментариями о том, что отправителю следует избегать RTTI по ​​причинам дизайна. Однако есть веские причины для использования RTTI (в основном из-за boost :: any). Имея это в виду, полезно знать фактическое использование ресурсов в распространенных реализациях.

Недавно я провел несколько исследований RTTI в GCC.

tl; dr: RTTI в GCC занимает ничтожно мало места, а typeid(a) == typeid(b) работает очень быстро на многих платформах (Linux, BSD и, возможно, встроенные платформы, но не mingw32). Если вы знаете, что всегда будете на благословенной платформе, RTTI очень близок к бесплатному.

Мелкие детали:

GCC предпочитает использовать конкретный "независимый от производителя" C ++ ABI [1] и всегда использует этот ABI для целей Linux и BSD [2]. Для платформ, которые поддерживают этот ABI, а также слабую связь, typeid() возвращает согласованный и уникальный объект для каждого типа, даже через границы динамического связывания. Вы можете протестировать &typeid(a) == &typeid(b) или просто положиться на тот факт, что переносимый тест typeid(a) == typeid(b) на самом деле просто сравнивает указатель внутри.

В предпочтительном ABI GCC класс vtable всегда содержит указатель на структуру RTTI для каждого типа, хотя он может не использоваться. Таким образом, сам вызов typeid() должен стоить столько же, сколько любой другой поиск vtable (так же, как вызов виртуальной функции-члена), а поддержка RTTI не должна использовать дополнительное пространство для каждого объекта.

Насколько я могу судить, структуры RTTI, используемые GCC (это все подклассы std::type_info), содержат только несколько байтов для каждого типа, помимо имени. Мне неясно, присутствуют ли имена в выходном коде даже с -fno-rtti. В любом случае изменение размера скомпилированного двоичного файла должно отражать изменение использования памяти во время выполнения.

Быстрый эксперимент (с использованием GCC 4.4.3 в 64-битной Ubuntu 10.04) показывает, что -fno-rtti на самом деле увеличивает двоичный размер простой тестовой программы на несколько сотен байтов. Это происходит постоянно при использовании комбинаций -g и -O3. Я не уверен, почему размер увеличился; одна возможность состоит в том, что код STL GCC ведет себя иначе без RTTI (поскольку исключения не работают).

[1] Известный как Itanium C ++ ABI, документированный на http://www.codesourcery.com/public/cxx-abi/abi.html. Имена ужасно сбивают с толку: имя относится к исходной архитектуре разработки, хотя спецификация ABI работает на многих архитектурах, включая i686 / x86_64. Комментарии во внутреннем исходном коде GCC и коде STL относятся к Itanium как к «новому» ABI в отличие от «старого», который они использовали раньше. Хуже того, «новый» / Itanium ABI относится ко всем версиям, доступным через -fabi-version; «старый» ABI предшествовал этому управлению версиями. GCC принял Itanium / versioned / "новый" ABI в версии 3.0; "старый" ABI использовался в версии 2.95 и ранее, если я правильно читаю их журналы изменений.

[2] Я не смог найти ни одного ресурса, в котором перечислены std::type_info стабильность объектов по платформам. Для компиляторов, к которым у меня был доступ, я использовал следующее: echo "#include <typeinfo>" | gcc -E -dM -x c++ -c - | grep GXX_MERGED_TYPEINFO_NAMES. Этот макрос управляет поведением operator== для std::type_info в GCC STL, начиная с GCC 3.0. Я обнаружил, что mingw32-gcc подчиняется Windows C ++ ABI, где std::type_info объекты не уникальны для типа в библиотеках DLL; typeid(a) == typeid(b) звонит strcmp под одеяло. Я предполагаю, что в однопрограммных встроенных целях, таких как AVR, где нет кода для связывания, std::type_info объекты всегда стабильны.

person sbrudenell    schedule 02.12.2010
comment
Исключения работают без RTTI. (Вам разрешено бросить int, и в нем нет vtable :)) - person Billy ONeal; 20.02.2014
comment
@BillyONeal: Да, исключения работают без vtables. Без RTTI они не могут работать, извините, что разочаровал. - person Deduplicator; 11.05.2014
comment
@Deduplicator: И все же, когда я выключаю RTTI в своем компиляторе, они работают нормально. Извините, что разочаровал вас. - person Billy ONeal; 12.05.2014
comment
@Deduplicator: (что опять же имеет смысл, потому что вы можете бросать такие вещи, как int, которые не могут участвовать в RTTI, потому что у них нет vtable) - person Billy ONeal; 12.05.2014
comment
@BillyONeal: Когда вы отключаете RTTI, действительно отключаются только его части. В частности, все выброшенные объекты и их базовые классы, где это применимо (рекурсивно), по-прежнему генерируют свои RTTI. - person Deduplicator; 12.05.2014
comment
@Deduplicator: это не имеет смысла для int, который не является базовым классом и не имеет базового класса. Компилятор может реализовать обработку исключений с использованием некоторых функций RTTI, но это ни в коем случае не требуется. - person Billy ONeal; 12.05.2014
comment
Механизм обработки исключений должен уметь работать с любым типом, выполняя несколько основных требований. Вы можете предложить, как обрабатывать выбросы и перехват исключений произвольного типа через границы модуля без RTTI. Учтите, что требуется литье вверх и вниз. - person Deduplicator; 12.05.2014
comment
Погодите, что имеется в виду, вы всегда можете сэкономить на времени выполнения, если можете себе позволить ...? Если первая версия быстрее, чем вторая, какова стоимость перехода? Что я должен себе позволить? (Просто хочу убедиться, что не упускаю некоторые недостатки, например нестабильность.) - person Cory-G; 25.08.2014
comment
typeid (a) == typeid (b) НЕ совпадает с B * ba = dynamic_cast ‹B *› (& a). Попробуйте применить его к объектам с множественным наследованием в качестве случайного уровня в дереве производных классов, и вы обнаружите, что typeid () == typeid () не даст положительного результата. dynamic_cast - единственный способ реально найти дерево наследования. Перестаньте думать о потенциальной экономии, отключив RTTI, и просто используйте его. Если у вас слишком много возможностей, оптимизируйте раздутый код. Старайтесь избегать использования dynamic_cast внутри внутренних циклов или любого другого кода, критичного к производительности, и все будет в порядке. - person mysticcoder; 19.12.2014
comment
@mcoder Вот почему в статье прямо говорится, что the latter necessarily involves traversing an inheritance tree plus comparisons. @CoryB Вы можете себе это позволить, если вам не нужно поддерживать приведение из всего дерева наследования. Например, если вы хотите найти все элементы типа X в коллекции, но не те, которые являются производными от X, то вам следует использовать первое. Если вам нужно также найти все производные экземпляры, вам придется использовать последний. - person Aidiakapi; 25.12.2014
comment
@mysticcoder действительно. или рассмотрите polymorphic_downcast<> - person v.oddou; 01.06.2017
comment
@mysticcoder, спасибо. Я тоже сомневаюсь в правильности. почему за такой неправильный ответ все еще так много голосов ... - person wuhaochi; 19.07.2018
comment
@wuhaochi есть посылка (возможно, не совсем ясная): вы всегда можете сэкономить на времени выполнения , если можете себе это позволить, бывают случаи, когда он делает то, что вам нужно, например, вы можете позволить себе использовать во-первых, в противном случае посылка неприменима. Я согласен, что это непонятно, но не обязательно неправильно - person 463035818_is_not_a_number; 01.07.2019

Возможно, эти цифры помогут.

Я проводил быстрый тест, используя это:

  • GCC Clock () + Профайлер XCode.
  • 100000000 итераций цикла.
  • 2 двухъядерных процессора Intel Xeon с тактовой частотой 2,66 ГГц.
  • Рассматриваемый класс является производным от одного базового класса.
  • typeid (). name () возвращает N12fastdelegate13FastDelegate1IivEE

Было протестировано 5 кейсов:

1) dynamic_cast< FireType* >( mDelegate )
2) typeid( *iDelegate ) == typeid( *mDelegate )
3) typeid( *iDelegate ).name() == typeid( *mDelegate ).name()
4) &typeid( *iDelegate ) == &typeid( *mDelegate )
5) { 
       fastdelegate::FastDelegateBase *iDelegate;
       iDelegate = new fastdelegate::FastDelegate1< t1 >;
       typeid( *iDelegate ) == typeid( *mDelegate )
   }

5 - это просто мой реальный код, так как мне нужно было создать объект этого типа, прежде чем проверять, похож ли он на тот, который у меня уже есть.

Без оптимизации

Для которых результаты были (я усреднил несколько прогонов):

1)  1,840,000 Ticks (~2  Seconds) - dynamic_cast
2)    870,000 Ticks (~1  Second)  - typeid()
3)    890,000 Ticks (~1  Second)  - typeid().name()
4)    615,000 Ticks (~1  Second)  - &typeid()
5) 14,261,000 Ticks (~23 Seconds) - typeid() with extra variable allocations.

Итак, вывод был бы такой:

  • Для простых случаев приведения без оптимизации typeid() более чем в два раза быстрее, чем dyncamic_cast.
  • На современной машине разница между ними составляет около 1 наносекунды (миллионная часть миллисекунды).

С оптимизацией (-Os)

1)  1,356,000 Ticks - dynamic_cast
2)     76,000 Ticks - typeid()
3)     76,000 Ticks - typeid().name()
4)     75,000 Ticks - &typeid()
5)     75,000 Ticks - typeid() with extra variable allocations.

Итак, вывод был бы такой:

  • Для простых случаев приведения с оптимизацией typeid() почти в 20 раз быстрее, чем dyncamic_cast.

Диаграмма

введите описание изображения здесь

Код

Как просили в комментариях, код ниже (немного беспорядочно, но работает). "FastDelegate.h" доступен по здесь .

#include <iostream>
#include "FastDelegate.h"
#include "cycle.h"
#include "time.h"

// Undefine for typeid checks
#define CAST

class ZoomManager
{
public:
    template < class Observer, class t1 >
    void Subscribe( void *aObj, void (Observer::*func )( t1 a1 ) )
    {
        mDelegate = new fastdelegate::FastDelegate1< t1 >;
        
        std::cout << "Subscribe\n";
        Fire( true );
    }
    
    template< class t1 >
    void Fire( t1 a1 )
    {
        fastdelegate::FastDelegateBase *iDelegate;
        iDelegate = new fastdelegate::FastDelegate1< t1 >;
        
        int t = 0;
        ticks start = getticks();
        
        clock_t iStart, iEnd;
        
        iStart = clock();
        
        typedef fastdelegate::FastDelegate1< t1 > FireType;
        
        for ( int i = 0; i < 100000000; i++ ) {
        
#ifdef CAST
                if ( dynamic_cast< FireType* >( mDelegate ) )
#else
                // Change this line for comparisons .name() and & comparisons
                if ( typeid( *iDelegate ) == typeid( *mDelegate ) )
#endif
                {
                    t++;
                } else {
                    t--;
                }
        }
        
        iEnd = clock();
        printf("Clock ticks: %i,\n", iEnd - iStart );
        
        std::cout << typeid( *mDelegate ).name()<<"\n";
        
        ticks end = getticks();
        double e = elapsed(start, end);
        std::cout << "Elasped: " << e;
    }
    
    template< class t1, class t2 >
    void Fire( t1 a1, t2 a2 )
    {
        std::cout << "Fire\n";
    }
    
    fastdelegate::FastDelegateBase *mDelegate;
};

class Scaler
{
public:
    Scaler( ZoomManager *aZoomManager ) :
        mZoomManager( aZoomManager ) { }
    
    void Sub()
    {
        mZoomManager->Subscribe( this, &Scaler::OnSizeChanged );
    }
    
    void OnSizeChanged( int X  )
    {
        std::cout << "Yey!\n";        
    }
private:
    ZoomManager *mZoomManager;
};

int main(int argc, const char * argv[])
{
    ZoomManager *iZoomManager = new ZoomManager();
    
    Scaler iScaler( iZoomManager );
    iScaler.Sub();
        
    delete iZoomManager;

    return 0;
}
person Izhaki    schedule 15.12.2012
comment
Конечно, динамическое приведение более общее - оно работает, если элемент более производный. Например. class a {}; class b : public a {}; class c : public b {};, когда целью является экземпляр c, будет нормально работать при тестировании для класса b с dynamic_cast, но не с решением typeid. Тем не менее, разумно, +1 - person Billy ONeal; 24.07.2013
comment
Можете ли вы разместить свой код на http://ideone.com/ или на подобном? Я хочу что-то протестировать. - person bobobobo; 29.10.2013
comment
Этот тест полностью поддельный с оптимизациями: проверка typeid инвариантна к циклу и исключена из цикла. Это совсем не интересно, это базовый тест-нет-нет. - person Kuba hasn't forgotten Monica; 20.02.2014
comment
@Kuba: Тогда эталонный тест - подделка. Это не повод для тестирования с отключенной оптимизацией; это повод писать лучшие тесты. - person Billy ONeal; 20.02.2014
comment
опять же, это провал. Для простых случаев приведения с оптимизацией typeid () почти в 20 раз быстрее, чем dyncamic_cast. они НЕ делают то же самое. Есть причина, по которой dynamic_cast работает медленнее. - person mysticcoder; 19.12.2014
comment
@KubaOber: всего +1. это так классика. и по количеству циклов должно быть очевидно, что это произошло. - person v.oddou; 07.04.2015
comment
Интересно, почему dynamic_cast не был оптимизирован и вне цикла. - person SamB; 13.02.2016
comment
@SamB может быть связано со строгим псевдонимом указателя mDelegate. - person Johann Gerell; 27.05.2016
comment
@SamB Скорее всего, потому что mDelegate не является константным указателем. Поскольку указатель может измениться и потенциально может указывать на другой подтип, dynamic_cast необходимо пересчитывать на каждой итерации. - person mascoj; 01.06.2017

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

Например, в псевдо-C ++:

struct Base
{
    virtual ~Base() {}
};

struct Derived
{
    virtual ~Derived() {}
};


int main()
{
    Base *d = new Derived();
    const char *name = typeid(*d).name(); // C++ way

    // faked up way (this won't actually work, but gives an idea of what might be happening in some implementations).
    const vtable *vt = reinterpret_cast<vtable *>(d);
    type_info *ti = vt->typeinfo;
    const char *name = ProcessRawName(ti->name);       
}

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

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

person Eclipse    schedule 23.02.2009
comment
+1 за то, что на самом деле объясняет, почему использование RTTI считается плохим дизайнерским решением, что раньше мне было не совсем понятно. - person jake; 26.06.2013
comment
Этот ответ представляет собой низкоуровневое понимание мощи C ++. В общем и в большинстве реализаций, если они используются широко, это означает, что вы не думаете о том, как правильно использовать языковые функции. Виртуальные функции и повторная реализация RTTI - это не ответ. RTTI - это ответ. Иногда вам просто нужно знать, относится ли объект к определенному типу. Вот почему он там! Таким образом, вы теряете несколько килобайт ОЗУ из-за некоторых структур type_info. Ну и дела ... - person mysticcoder; 19.12.2014

Что ж, профайлер никогда не врет.

Поскольку у меня есть довольно стабильная иерархия из 18-20 типов, которая не очень сильно меняется, я подумал, поможет ли простое использование enum'd member и избежать якобы "высокой" стоимости RTTI. Я скептически относился к тому, что RTTI на самом деле дороже, чем просто вводимое в нем выражение if. Мальчик, о, мальчик, не так ли.

Оказывается, RTTI дороже, гораздо дороже, чем эквивалентный if оператор или простой switch для примитивной переменной в C ++. Итак, ответ С.Лотта не совсем правильный, существует дополнительная плата за RTTI, и это нет из-за того, что наличие оператора if в смешивание. Это связано с тем, что RTTI стоит очень дорого.

Этот тест проводился на компиляторе Apple LLVM 5.0 с включенной стандартной оптимизацией (настройки режима выпуска по умолчанию).

Итак, у меня есть две функции, каждая из которых определяет конкретный тип объекта через 1) RTTI или 2) простой переключатель. Это происходит 50 000 000 раз. Без лишних слов, я представляю вам относительное время выполнения для 50 000 000 запусков.

введите описание изображения здесь

Правильно, dynamicCasts занял 94% времени выполнения. В то время как блок regularSwitch занял всего 3,3%.

Короче говоря: если вы можете позволить себе энергию для подключения типа enum'd, как я сделал ниже, я, вероятно, порекомендовал бы это, если вам нужно сделать RTTI и производительность имеет первостепенное значение. Требуется всего лишь установить член один раз (убедитесь, что вы получили его через все конструкторы), и никогда не записывайте его позже.

Тем не менее, это не должно испортить вашу практику ООП. это предназначено для использования только тогда, когда информация о типе просто недоступна, и вы оказались в затруднительном положении в использовании RTTI.

#include <stdio.h>
#include <vector>
using namespace std;

enum AnimalClassTypeTag
{
  TypeAnimal=1,
  TypeCat=1<<2,TypeBigCat=1<<3,TypeDog=1<<4
} ;

struct Animal
{
  int typeTag ;// really AnimalClassTypeTag, but it will complain at the |= if
               // at the |='s if not int
  Animal() {
    typeTag=TypeAnimal; // start just base Animal.
    // subclass ctors will |= in other types
  }
  virtual ~Animal(){}//make it polymorphic too
} ;

struct Cat : public Animal
{
  Cat(){
    typeTag|=TypeCat; //bitwise OR in the type
  }
} ;

struct BigCat : public Cat
{
  BigCat(){
    typeTag|=TypeBigCat;
  }
} ;

struct Dog : public Animal
{
  Dog(){
    typeTag|=TypeDog;
  }
} ;

typedef unsigned long long ULONGLONG;

void dynamicCasts(vector<Animal*> &zoo, ULONGLONG tests)
{
  ULONGLONG animals=0,cats=0,bigcats=0,dogs=0;
  for( ULONGLONG i = 0 ; i < tests ; i++ )
  {
    for( Animal* an : zoo )
    {
      if( dynamic_cast<Dog*>( an ) )
        dogs++;
      else if( dynamic_cast<BigCat*>( an ) )
        bigcats++;
      else if( dynamic_cast<Cat*>( an ) )
        cats++;
      else //if( dynamic_cast<Animal*>( an ) )
        animals++;
    }
  }

  printf( "%lld animals, %lld cats, %lld bigcats, %lld dogs\n", animals,cats,bigcats,dogs ) ;

}

//*NOTE: I changed from switch to if/else if chain
void regularSwitch(vector<Animal*> &zoo, ULONGLONG tests)
{
  ULONGLONG animals=0,cats=0,bigcats=0,dogs=0;
  for( ULONGLONG i = 0 ; i < tests ; i++ )
  {
    for( Animal* an : zoo )
    {
      if( an->typeTag & TypeDog )
        dogs++;
      else if( an->typeTag & TypeBigCat )
        bigcats++;
      else if( an->typeTag & TypeCat )
        cats++;
      else
        animals++;
    }
  }
  printf( "%lld animals, %lld cats, %lld bigcats, %lld dogs\n", animals,cats,bigcats,dogs ) ;  

}

int main(int argc, const char * argv[])
{
  vector<Animal*> zoo ;

  zoo.push_back( new Animal ) ;
  zoo.push_back( new Cat ) ;
  zoo.push_back( new BigCat ) ;
  zoo.push_back( new Dog ) ;

  ULONGLONG tests=50000000;

  dynamicCasts( zoo, tests ) ;
  regularSwitch( zoo, tests ) ;
}
person bobobobo    schedule 29.10.2013

Стандартный способ:

cout << (typeid(Base) == typeid(Derived)) << endl;

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

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

cout << (typeid(Base).name() == typeid(Derived).name()) << endl;

Это не гарантирует работу (никогда не даст ложных срабатываний, но может дать ложноотрицательные результаты), но может быть до 15 раз быстрее. Это зависит от реализации typeid () для работы определенным образом, и все, что вы делаете, это сравнивает внутренний указатель char. Иногда это также эквивалентно:

cout << (&typeid(Base) == &typeid(Derived)) << endl;

Однако вы можете безопасно использовать гибрид, который будет очень быстрым, если типы совпадают, и будет худшим случаем для несовпадающих типов:

cout << ( typeid(Base).name() == typeid(Derived).name() || 
          typeid(Base) == typeid(Derived) ) << endl;

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

Самый безопасный способ оптимизировать это - реализовать свой собственный typeid как int (или перечисление Type: int) как часть вашего базового класса и использовать его для определения типа класса, а затем просто использовать static_cast ‹> или reinterpret_cast‹ >

Для меня разница примерно в 15 раз на неоптимизированном MS VS 2005 C ++ SP1.

person Marius    schedule 23.09.2009
comment
Стандартный RTTI стоит дорого, потому что он основан на сравнении базовой строки - нет, в этом нет ничего стандартного; это просто как работают typeid::operators вашей реализации. Например, GCC на поддерживаемой платформе уже использует сравнения char *s без нашего принуждения - gcc.gnu.org/onlinedocs/gcc-4.6.3/libstdc ++ / api /. Конечно, ваш способ заставляет MSVC вести себя намного лучше, чем по умолчанию на вашей платформе, так что спасибо, и я не знаю, каковы некоторые цели, которые изначально используют указатели ... но я хочу сказать, что поведение MSVC ни в коем случае не является стандартным. - person underscore_d; 26.04.2016

Для простой проверки RTTI может быть столь же дешевым, как сравнение указателей. Для проверки наследования это может быть столь же затратно, как strcmp для каждого типа в дереве наследования, если вы dynamic_cast двигаетесь сверху вниз в одной реализации.

Вы также можете уменьшить накладные расходы, не используя dynamic_cast и вместо этого явно проверяя тип с помощью & typeid (...) == & typeid (type). Хотя это не обязательно работает для .dll или другого динамически загружаемого кода, это может быть довольно быстро для вещей, которые статически связаны.

Хотя в этот момент это похоже на использование оператора switch, так что готово.

person MSN    schedule 24.02.2009
comment
Есть ли у вас ссылки на версию strcmp? Кажется крайне неэффективным и неточным использовать strcmp для проверки типа. - person JaredPar; 24.02.2009
comment
В плохой реализации, которая могла бы иметь несколько объектов type_info для каждого типа, она могла бы реализовать bool type_info :: operator == (const type_info & x) const как! Strcmp (name (), x.name ()) - person Greg Rogers; 24.02.2009
comment
Приступите к разборке dynamic_cast или typeid (). Operator == для MSVC, и вы попадете в strcmp. Я предполагаю, что это там для ужасного случая, когда вы сравниваете с типом, скомпилированным в другой .dll. И он использует искаженное имя, так что, по крайней мере, оно правильное для того же компилятора. - person MSN; 24.02.2009
comment
вы должны делать typeid (...) == typeid (type), а не сравнивать адрес - person Johannes Schaub - litb; 24.02.2009
comment
Я хочу сказать, что вы можете сделать & typeid (...) == & typeid (blah) как можно раньше, и это будет безопасно. На самом деле он может не делать ничего полезного, поскольку typeid (...) может быть сгенерирован в стеке, но если их адреса равны, то их типы равны. - person MSN; 24.02.2009
comment
Почему вы не можете использовать typeid в ddls? - person Ari; 10.03.2021

Всегда лучше измерять вещи. В следующем коде под g ++ использование кодированной вручную идентификации типа кажется примерно в три раза быстрее, чем RTTI. Я уверен, что более реалистичная реализация с ручным кодированием, использующая строки вместо символов, будет медленнее, сближая тайминги ..

#include <iostream>
using namespace std;

struct Base {
    virtual ~Base() {}
    virtual char Type() const = 0;
};

struct A : public Base {
    char Type() const {
        return 'A';
    }
};

struct B : public Base {;
    char Type() const {
        return 'B';
    }
};

int main() {
    Base * bp = new A;
    int n = 0;
    for ( int i = 0; i < 10000000; i++ ) {
#ifdef RTTI
        if ( A * a = dynamic_cast <A*> ( bp ) ) {
            n++;
        }
#else
        if ( bp->Type() == 'A' ) {
            A * a = static_cast <A*>(bp);
            n++;
        }
#endif
    }
    cout << n << endl;
}
person Community    schedule 24.02.2009
comment
старайтесь делать это не с dynamic_cast, а с typeid. это может ускорить работу. - person Johannes Schaub - litb; 24.02.2009
comment
но использование dynamic_cast более реалистично, по крайней мере, если посмотреть на мой код - person ; 24.02.2009
comment
он делает другое: он также проверяет, указывает ли bp на тип, производный от A. your == 'A' проверяет, указывает ли он точно на 'A'. Я также считаю, что этот тест в некоторой степени несправедлив: компилятор легко видит, что bp не может указывать ни на что иное, кроме A., но я думаю, что здесь он не оптимизируется. - person Johannes Schaub - litb; 24.02.2009
comment
в любом случае, я проверил ваш код. и дает мне 0,016 с для RTTI и 0,044 с для вызовов виртуальных функций. (с использованием -O2) - person Johannes Schaub - litb; 24.02.2009
comment
хотя изменение его на использование typeid здесь не имеет никакого значения (все еще 0,016 с) - person Johannes Schaub - litb; 24.02.2009
comment
Как насчет потребления памяти? - person Cristián Romo; 24.02.2009
comment
@cristian в любом случае количество, используемое для информации о типе, будет крошечным - person ; 24.02.2009

Некоторое время назад я измерил временные затраты на RTTI в конкретных случаях MSVC и GCC для PowerPC с частотой 3 ГГц. В тестах, которые я запускал (довольно большое приложение C ++ с глубоким деревом классов), каждое dynamic_cast<> стоило от 0,8 до 2 с, в зависимости от того, сработало оно или нет.

person Crashworks    schedule 02.12.2010

Итак, насколько дорого стоит RTTI?

Это полностью зависит от используемого вами компилятора. Я понимаю, что некоторые используют сравнения строк, а другие используют реальные алгоритмы.

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

person Max Lybbert    schedule 24.02.2009

RTTI может быть дешевым и не обязательно требует strcmp. Компилятор ограничивает тест выполнением фактической иерархии в обратном порядке. Итак, если у вас есть класс C, который является дочерним элементом класса B, который является дочерним элементом класса A, dynamic_cast от A * ptr до C * ptr подразумевает только одно сравнение указателей, а не два (BTW, только указатель таблицы vptr в сравнении). Тест похож на "if (vptr_of_obj == vptr_of_C) return (C *) obj"

Другой пример, если мы попытаемся выполнить dynamic_cast из A * в B *. В этом случае компилятор будет проверять оба регистра по очереди (obj - C, а obj - B). Это также можно упростить до одного теста (в большинстве случаев), поскольку таблица виртуальных функций создается как агрегирование, поэтому тест возобновляется с «if (offset_of (vptr_of_obj, B) == vptr_of_B)» с

offset_of = вернуть sizeof (vptr_table)> = sizeof (vptr_of_B)? vptr_of_new_methods_in_B: 0

Схема памяти

vptr_of_C = [ vptr_of_A | vptr_of_new_methods_in_B | vptr_of_new_methods_in_C ]

Как компилятор узнает об оптимизации во время компиляции?

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

Например, это не компилируется:

void * something = [...]; 
// Compile time error: Can't convert from something to MyClass, no hierarchy relation
MyClass * c = dynamic_cast<MyClass*>(something);  
person X-Ryl669    schedule 26.08.2010

RTTI может быть «дорогостоящим», потому что вы добавляете оператор if каждый раз, когда выполняете сравнение RTTI. В глубоко вложенных итерациях это может быть дорого. В том, что никогда не выполняется в цикле, это по сути бесплатно.

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

RTTI также является дорогостоящим, поскольку может скрыть иерархию подклассов (если она вообще существует). Побочным эффектом этого может быть удаление «объектно-ориентированного» из «объектно-ориентированного программирования».

person S.Lott    schedule 23.02.2009
comment
Не обязательно - я собирался использовать его косвенно через dynamic_cast и сохранить иерархию на месте, потому что мне нужно понижать, потому что каждый подтип должен иметь разные (переменного размера) данные, которые должны применяться по-разному, следовательно, dynamic_cast. - person Cristián Romo; 24.02.2009
comment
@ Cristián Romo: Пожалуйста, обновите свой вопрос этими новыми фактами. dynamic_cast - это (иногда) необходимое зло в C ++. Спрашивать о производительности RTTI, когда вы вынуждены это делать, не имеет большого смысла. - person S.Lott; 24.02.2009
comment
@ S.Lott: Обновлено. Извините за путаницу. - person Cristián Romo; 24.02.2009
comment
Я только что провел эксперимент по этому поводу - оказалось, что RTTI значительно дороже, чем выражение if, которое вы вводите, когда таким образом проверьте информацию о типе среды выполнения. - person bobobobo; 29.10.2013