我知道使用RTTI会造成资源损失,但它有多大?我看起来只是说"RTTI很贵",但它们实际上都没有给出任何基准或定量数据来控制内存,处理器时间或速度.
那么,RTTI有多贵?我可能会在我只有4MB RAM的嵌入式系统上使用它,所以每一位都很重要.
编辑:根据S. Lott的回答,如果我包括我实际做的事情会更好. 我正在使用一个类传递不同长度的数据,并且可以执行不同的操作,因此仅使用虚函数很难做到这一点.似乎使用少数dynamic_cast
s可以通过允许不同的派生类通过不同的级别但仍然允许它们以完全不同的方式行动来解决这个问题.
根据我的理解,dynamic_cast
使用RTTI,所以我想知道在有限的系统上使用它是多么可行.
无论编译器如何,如果你能负担得起,你总是可以节省运行时间
if (typeid(a) == typeid(b)) { B* ba = static_cast(&a); etc; }
代替
B* ba = dynamic_cast(&a); if (ba) { etc; }
前者只涉及一个比较std::type_info
; 后者必然涉及遍历继承树和比较.
过去......就像每个人都说的那样,资源使用是特定于实现的.
我同意其他人的意见,提交者应出于设计原因避免使用RTTI.然而,在使用RTTI(主要是因为升压::任何的)很好的理由.请注意,了解常见实现中的实际资源使用情况很有用.
我最近在GCC中对RTTI做了大量研究.
tl;博士:GCC中的RTTI在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)
实际上只是在内部比较指针的事实.
在GCC首选的ABI中,类vtable 始终保存指向每类型RTTI结构的指针,尽管它可能不会被使用.因此,typeid()
调用本身应该只花费任何其他vtable查找(与调用虚拟成员函数相同),并且RTTI支持不应该为每个对象使用任何额外的空间.
从我可以看出,GCC使用的RTTI结构(这些是它们的所有子类std::type_info
)除了名称之外,每个类型只保留几个字节.我不清楚输出代码中是否存在名称-fno-rtti
.无论哪种方式,编译二进制文件的大小变化都应反映运行时内存使用量的变化.
快速实验(在Ubuntu 10.04 64位上使用GCC 4.4.3)表明-fno-rtti
实际上将简单测试程序的二进制大小增加了几百个字节.这种情况在-g
和的组合中一致地发生-O3
.我不确定为什么尺寸会增加; 一种可能性是GCC的STL代码在没有RTTI的情况下表现不同(因为异常不起作用).
[1] Known as the Itanium C++ ABI, documented at http://www.codesourcery.com/public/cxx-abi/abi.html. The names are horribly confusing: the name refers to the original development architecture, though the ABI specification works on lots of architectures including i686/x86_64. Comments in GCC's internal source and STL code refer to Itanium as the "new" ABI in contrast to the "old" one they used before. Worse, the "new"/Itanium ABI refers to all versions available through -fabi-version
; the "old" ABI predated this versioning. GCC adopted the Itanium/versioned/"new" ABI in version 3.0; the "old" ABI was used in 2.95 and earlier, if I am reading their changelogs correctly.
[2]我无法std::type_info
通过平台找到任何资源列表对象的稳定性.对于我有权访问的编译器,我使用了以下内容:echo "#include
.此宏控制GCC的STL中的operator==
for 的行为std::type_info
,从GCC 3.0开始.我确实发现mingw32-gcc服从Windows C++ ABI,其中std::type_info
对象并不是DLL中类型的唯一对象; typeid(a) == typeid(b)
打电话strcmp
.我推测在像AVR这样的单程序嵌入式目标上,没有可以链接的代码,std::type_info
对象总是稳定的.
也许这些数字会有所帮助.
我正在使用这个进行快速测试:
GCC Clock()+ XCode的Profiler.
100,000,000次循环迭代.
2 x 2.66 GHz双核Intel Xeon.
有问题的类派生自单个基类.
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()
比快速快x20 dyncamic_cast
.
图表
代码根据评论中的要求,代码如下(有点乱,但有效).'FastDelegate.h'可从此处获得.
#include#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; }
这取决于事物的规模.在大多数情况下,它只是几个检查和几个指针解引用.在大多数实现中,在具有虚函数的每个对象的顶部,存在指向vtable的指针,该vtable包含指向该类上的虚函数的所有实现的指针列表.我猜大多数实现都会使用它来存储另一个指向类的type_info结构的指针.
例如在pseudo-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(d); type_info *ti = vt->typeinfo; const char *name = ProcessRawName(ti->name); }
一般来说,反对RTTI的真正论据是每次添加新派生类时都必须修改代码的不可维护性.而不是在任何地方使用switch语句,将它们分解为虚函数.这会将类之间不同的所有代码移动到类本身中,这样新的派生只需覆盖所有虚函数即可成为一个功能齐全的类.如果您每次有人检查类的类型并执行不同的操作时,您曾经不得不通过大型代码库进行搜索,那么您将很快学会远离这种编程风格.
如果你的编译器让你完全关闭RTTI,那么在这么小的RAM空间下,最终节省的代码大小可能会很大.编译器需要为每个具有虚函数的类生成一个type_info结构.如果关闭RTTI,则不需要将所有这些结构都包含在可执行映像中.
好吧,探查器永远不会说谎.
由于我有一个非常稳定的18-20种类型的层次结构并没有太大变化,我想知道是否只使用一个简单的enum'd成员就可以做到这一点并避免据称"高"的RTTI成本.如果RTTI实际上比if
它引入的声明更昂贵,我持怀疑态度.男孩哦,男孩,是吗.
事实证明,RTTI 是昂贵,更比同等的昂贵if
语句或一个简单的switch
在C++一个原始变量.所以美国洛特的答案并不完全正确,还有就是对RTTI额外的费用,这是不是由于只是有一个if
声明中拌匀.这是因为RTTI非常昂贵.
此测试在Apple LLVM 5.0编译器上完成,库存优化已打开(默认发布模式设置).
所以,我有2个以下的函数,每个函数通过1)RTTI或2)一个简单的开关来计算对象的具体类型.它这样做了50,000,000次.不用多说,我向你展示50,000,000次运行的相对运行时间.
这是正确的,将dynamicCasts
采取94%的运行时间.虽然该regularSwitch
块仅占3.3%.
简而言之:如果你能够enum
像我下面所做的那样能够负担得起'd型',我可能会推荐它,如果你需要做RTTI 而性能是至关重要的.它只需要设置一次成员(确保通过所有构造函数获取它),并确保之后永远不会写它.
也就是说,这样做不应该搞乱你的OOP实践..它只是意味着在类型信息根本不可用时使用,你发现自己被逼入使用RTTI.
#include#include 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 &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 ( an ) ) dogs++; else if( dynamic_cast ( an ) ) bigcats++; else if( dynamic_cast ( an ) ) cats++; else //if( dynamic_cast ( 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 &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 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 ) ; }
标准方式:
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)作为Base类的一部分,并使用它来确定类的类型,然后使用static_cast <>或reinterpret_cast < >
对我来说,未经优化的MS VS 2005 C++ SP1的差异大约是15倍.
对于简单的检查,RTTI可以像指针比较一样便宜.对于继承检查,strcmp
如果你dynamic_cast
在一个实现中从上到下,它对于继承树中的每个类型都可能是昂贵的.
您还可以通过不使用dynamic_cast
而不是通过&typeid(...)==&typeid(type)显式检查类型来减少开销.虽然这不一定适用于.dll或其他动态加载的代码,但对于静态链接的东西来说它可能非常快.
虽然在那一点上它就像使用switch语句,所以你去吧.
衡量事物总是最好的.在下面的代码中,在g ++下,手动编码类型识别的使用似乎比RTTI快三倍.我确信使用字符串而不是字符的更逼真的手动编码实现会更慢,从而使时间更接近.
#includeusing 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 ( bp ) ) { n++; } #else if ( bp->Type() == 'A' ) { A * a = static_cast (bp); n++; } #endif } cout << n << endl; }