C++ RTTI有多贵?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/579887/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How expensive is RTTI?
提问by Cristián Romo
I understand that there is a resource hit from using RTTI, but how big is it? Everywhere I've looked just says that "RTTI is expensive," but none of them actually give any benchmarks or quantitative data reguarding memory, processor time, or speed.
我知道使用 RTTI 会影响资源,但它有多大?我看过的所有地方都只是说“RTTI 很昂贵”,但实际上没有一个提供任何基准或定量数据来保护内存、处理器时间或速度。
So, just how expensive is RTTI? I might use it on an embedded system where I have only 4MB of RAM, so every bit counts.
那么,RTTI 到底有多贵?我可能会在只有 4MB 内存的嵌入式系统上使用它,所以每一位都很重要。
Edit: As per S. Lott's answer, it would be better if I include what I'm actually doing. I am using a class to pass in data of different lengths and that can perform different actions, so it would be difficult to do this using only virtual functions. It seems that using a few dynamic_cast
s could remedy this problem by allowing the different derived classes to be passed through the different levels yet still allow them to act completely differently.
编辑:根据 S. Lott 的回答,如果我包括我实际在做的事情会更好。 我正在使用一个类来传递不同长度的数据并且可以执行不同的操作,因此仅使用虚函数很难做到这一点。似乎使用几个dynamic_cast
s 可以通过允许不同的派生类通过不同的级别但仍然允许它们完全不同的行为来解决这个问题。
From my understanding, dynamic_cast
uses RTTI, so I was wondering how feasable it would be to use on a limited system.
根据我的理解,dynamic_cast
使用 RTTI,所以我想知道在有限的系统上使用它有多可行。
回答by sbrudenell
Regardless of compiler, you can always save on runtime if you can afford to do
无论编译器如何,如果您负担得起,您始终可以节省运行时间
if (typeid(a) == typeid(b)) {
B* ba = static_cast<B*>(&a);
etc;
}
instead of
代替
B* ba = dynamic_cast<B*>(&a);
if (ba) {
etc;
}
The former involves only one comparison of std::type_info
; the latter necessarily involves traversing an inheritance tree plus comparisons.
前者只涉及以下一项比较std::type_info
;后者必然涉及遍历继承树和比较。
Past that ... like everyone says, the resource usage is implementation specific.
过去......就像每个人所说的那样,资源使用是特定于实现的。
I agree with everyone else's comments that the submitter should avoid RTTI for design reasons. However, there aregood reasons to use RTTI (mainly because of boost::any). That in mind, it's useful to know its actual resource usage in common implementations.
我同意其他人的意见,即提交者出于设计原因应避免使用 RTTI。然而,在使用RTTI(主要是因为升压::任何的)很好的理由。请记住,了解其在常见实现中的实际资源使用情况很有用。
I recently did a bunch of research into RTTI in GCC.
我最近对 GCC 中的 RTTI 进行了大量研究。
tl;dr: RTTI in GCC uses negligible space and typeid(a) == typeid(b)
is very fast, on many platforms (Linux, BSD and maybe embedded platforms, but not mingw32). If you know you'll always be on a blessed platform, RTTI is very close to free.
tl;dr:GCC 中的 RTTI 在typeid(a) == typeid(b)
许多平台(Linux、BSD 和嵌入式平台,但不是 mingw32)上使用的空间可以忽略不计并且速度非常快。如果您知道自己将永远在一个受祝福的平台上,那么 RTTI 非常接近免费。
Gritty details:
粗暴的细节:
GCC prefers to use a particular "vendor-neutral" C++ ABI[1], and always uses this ABI for Linux and BSD targets[2]. For platforms that support this ABI and also weak linkage, typeid()
returns a consistent and unique object for each type, even across dynamic linking boundaries. You can test &typeid(a) == &typeid(b)
, or just rely on the fact that the portable test typeid(a) == typeid(b)
does actually just compare a pointer internally.
GCC 更喜欢使用特定的“供应商中立”C++ ABI[1],并且始终将此 ABI 用于 Linux 和 BSD 目标[2]。对于支持此 ABI 和弱链接的平台typeid()
,即使跨越动态链接边界,也会为每种类型返回一致且唯一的对象。您可以 test &typeid(a) == &typeid(b)
,或者只是依赖于便携式测试typeid(a) == typeid(b)
实际上只是在内部比较指针的事实。
In GCC's preferred ABI, a class vtable alwaysholds a pointer to a per-type RTTI structure, though it might not be used. So a typeid()
call itself shouldonly cost as much as any other vtable lookup (the same as calling a virtual member function), and RTTI support shouldn'tuse any extra space for each object.
在 GCC 的首选 ABI 中,类 vtable总是保存一个指向每个类型的 RTTI 结构的指针,尽管它可能不被使用。因此,typeid()
调用本身应该只花费与任何其他 vtable 查找(与调用虚拟成员函数相同)的成本,并且 RTTI 支持不应为每个对象使用任何额外空间。
From what I can make out, the RTTI structures used by GCC (these are all the subclasses of std::type_info
) only hold a few bytes for each type, aside from the name. It isn't clear to me whether the names are present in the output code even with -fno-rtti
. Either way, the change in size of the compiled binary should reflect the change in runtime memory usage.
据我所知,GCC 使用的 RTTI 结构(这些都是 的子类std::type_info
)除了名称之外,每种类型只包含几个字节。我不清楚输出代码中是否存在名称,即使是-fno-rtti
. 无论哪种方式,编译后的二进制文件大小的变化都应该反映运行时内存使用的变化。
A quick experiment (using GCC 4.4.3 on Ubuntu 10.04 64-bit) shows that -fno-rtti
actually increasesthe binary size of a simple test program by a few hundred bytes. This happens consistently across combinations of -g
and -O3
. I'm not sure why the size would increase; one possibility is that GCC's STL code behaves differently without RTTI (since exceptions won't work).
一个快速实验(在 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 allversions 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.
[1] 称为 Itanium C++ ABI,记录在http://www.codesourcery.com/public/cxx-abi/abi.html。名称非常混乱:名称指的是原始开发架构,尽管 ABI 规范适用于许多架构,包括 i686/x86_64。GCC 的内部源代码和 STL 代码中的注释将 Itanium 称为“新”ABI,而不是他们以前使用的“旧”ABI。更糟的是,“新” /安腾ABI指的是所有可通过的版本-fabi-version
; “旧” ABI 早于此版本控制。GCC 在 3.0 版本中采用了 Itanium/versioned/"new" ABI;如果我正确阅读了他们的变更日志,则在 2.95 及更早版本中使用了“旧”ABI。
[2] I couldn't find any resource listing std::type_info
object stability by platform. For compilers I had access to, I used the following: echo "#include <typeinfo>" | gcc -E -dM -x c++ -c - | grep GXX_MERGED_TYPEINFO_NAMES
. This macro controls the behavior of operator==
for std::type_info
in GCC's STL, as of GCC 3.0. I did find that mingw32-gcc obeys the Windows C++ ABI, where std::type_info
objects aren't unique for a type across DLLs; typeid(a) == typeid(b)
calls strcmp
under the covers. I speculate that on single-program embedded targets like AVR, where there is no code to link against, std::type_info
objects are always stable.
[2] 我找不到任何std::type_info
按平台列出对象稳定性的资源。对于编译器我访问,我用下面的:echo "#include <typeinfo>" | gcc -E -dM -x c++ -c - | grep GXX_MERGED_TYPEINFO_NAMES
。这个宏控制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
对象总是稳定的。
回答by Izhaki
Perhaps these figures would help.
也许这些数字会有所帮助。
I was doing a quick test using this:
我正在使用这个进行快速测试:
- GCC Clock() + XCode's Profiler.
- 100,000,000 loop iterations.
- 2 x 2.66 GHz Dual-Core Intel Xeon.
- The class in question is derived from a single base class.
- typeid().name() returns "N12fastdelegate13FastDelegate1IivEE"
- GCC Clock() + XCode 的分析器。
- 100,000,000 次循环迭代。
- 2 个 2.66 GHz 双核英特尔至强。
- 有问题的类是从单个基类派生的。
- typeid().name() 返回“N12fastdelegate13FastDelegate1IivEE”
5 Cases were tested:
测试了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 is just my actual code, as I needed to create an object of that type before checking if it is similar to one I already have.
5 只是我的实际代码,因为我需要在检查它是否与我已有的相似之前创建该类型的对象。
Without Optimisation
没有优化
For which the results were (I've averaged a few runs):
结果是(我平均运行了几次):
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.
So the conclusion would be:
所以结论是:
- For simple cast cases without optimisation
typeid()
is more than twice faster thandyncamic_cast
. - On a modern machine the difference between the two is about 1 nanosecond (a millionth of a millisecond).
- 对于没有优化的简单强制转换情况,
typeid()
比dyncamic_cast
. - 在现代机器上,两者之间的差异约为 1 纳秒(百万分之一毫秒)。
With Optimisation (-Os)
优化 (-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.
So the conclusion would be:
所以结论是:
- For simple cast cases with optimisation,
typeid()
is nearly x20 faster thandyncamic_cast
.
- 对于经过优化的简单强制转换情况,
typeid()
比dyncamic_cast
.
Chart
图表
The Code
编码
As requested in the comments, the code is below (a bit messy, but works). 'FastDelegate.h' is available from here.
根据评论中的要求,代码如下(有点乱,但有效)。'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;
}
回答by Eclipse
It depends on the scale of things. For the most part it's just a couple checks and a few pointer dereferences. In most implementations, at the top of every object that has virtual functions, there is a pointer to a vtable that holds a list of pointers to all the implementations of the virtual function on that class. I would guess that most implementations would use this to either store another pointer to the type_info structure for the class.
这取决于事情的规模。在大多数情况下,它只是一些检查和一些指针取消引用。在大多数实现中,在每个具有虚函数的对象的顶部,都有一个指向 vtable 的指针,其中包含指向该类中所有虚函数实现的指针列表。我猜大多数实现会使用它来存储另一个指向类的 type_info 结构的指针。
For example in pseudo-c++:
例如在伪 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);
}
In general the real argument against RTTI is the unmaintainability of having to modify code everywhere every time you add a new derived class. Instead of switch statements everywhere, factor those into virtual functions. This moves all the code that is different between classes into the classes themselves, so that a new derivation just needs to override all the virtual functions to become a fully functioning class. If you've ever had to hunt through a large code base for every time someone checks the type of a class and does something different, you'll quickly learn to stay away from that style of programming.
一般来说,反对 RTTI 的真正理由是每次添加新的派生类时都必须在任何地方修改代码的不可维护性。不是到处都是 switch 语句,而是将它们分解为虚函数。这将所有在类之间不同的代码移动到类本身中,因此新的派生只需要覆盖所有虚函数即可成为一个功能齐全的类。如果每次有人检查类的类型并执行不同的操作时,您都不得不搜索大型代码库,那么您将很快学会远离这种编程风格。
If your compiler lets you totally turn off RTTI, the final resulting code size savings can be significant though, with such a small RAM space. The compiler needs to generate a type_info structure for every single class with a virtual function. If you turn off RTTI, all these structures do not need to be included in the executable image.
如果您的编译器允许您完全关闭 RTTI,那么最终产生的代码大小节省可能会很大,但 RAM 空间如此之小。编译器需要为每个具有虚函数的类生成一个 type_info 结构。如果关闭 RTTI,则所有这些结构都不需要包含在可执行映像中。
回答by bobobobo
Well, the profiler never lies.
好吧,分析器从不说谎。
Since I have a pretty stable hierarchy of 18-20 types that is not changing very much, I wondered if just using a simple enum'd memberwould do the trick and avoid the purportedly "high" cost of RTTI. I was skeptical if RTTI was in fact more expensive than just the if
statement it introduces. Boy oh boy, is it.
由于我有一个非常稳定的 18-20 种类型的层次结构,而且变化不大,我想知道是否只使用一个简单的枚举成员就可以解决问题并避免所谓的“高”RTTI 成本。我怀疑 RTTI 实际上是否比if
它介绍的声明更昂贵。男孩哦男孩,是吗。
It turns out that RTTI isexpensive, much moreexpensive than an equivalent if
statement or a simple switch
on a primitive variable in C++. So S.Lott's answer is not completely correct, there isextra cost for RTTI, and it's notdue to just having an if
statementin the mix. It's due to that RTTI is very expensive.
事实证明,RTTI是昂贵的,比C++ 中原始变量上的等效语句或简单语句要昂贵得多。所以美国洛特的答案并不完全正确,还有就是对RTTI额外的费用,这是不是由于只是有一个声明中拌匀。这是因为 RTTI 非常昂贵。if
switch
if
This test was done on the Apple LLVM 5.0 compiler, with stock optimizations turned on (default release mode settings).
此测试是在 Apple LLVM 5.0 编译器上完成的,启用了库存优化(默认发布模式设置)。
So, I have below 2 functions, each of which figures out the concrete type of an object either via 1) RTTI or 2) a simple switch. It does so 50,000,000 times. Without further ado, I present to you the relative runtimes for 50,000,000 runs.
所以,我有以下 2 个函数,每个函数都通过 1) RTTI 或 2) 一个简单的开关来计算出对象的具体类型。它这样做了 50,000,000 次。事不宜迟,我向您展示了 50,000,000 次运行的相对运行时间。
That's right, the dynamicCasts
took 94%of runtime. While the regularSwitch
block only took 3.3%.
没错,它dynamicCasts
占用了94%的运行时间。而regularSwitch
区块只占了3.3%。
Long story short: If you can afford the energy to hook-in an enum
'd type as I did below, I'd probably recommend it, if you need to do RTTI andperformance is paramount. It only takes setting the member once(make sure to get it via all constructors), and be sure to never write it afterward.
长话短说:如果你有能力enum
像我在下面所做的那样连接一个d 类型,我可能会推荐它,如果你需要做 RTTI并且性能是最重要的。它只需要设置一次成员(确保通过所有构造函数获取它),并且确保以后永远不要写它。
That said, doing this should not mess up your OOP practices..it's only meant to be used when type information simply isn't available and you find yourself cornered into using RTTI.
也就是说,这样做不应该弄乱您的 OOP 实践。它仅适用于类型信息根本不可用并且您发现自己陷入使用 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 ) ;
}
回答by Marius
The standard way:
标准方式:
cout << (typeid(Base) == typeid(Derived)) << endl;
Standard RTTI is expensive because it relies on doing a underlying string compare and thus the speed of RTTI can vary depending on the class name length.
标准 RTTI 很昂贵,因为它依赖于进行底层字符串比较,因此 RTTI 的速度可能因类名长度而异。
The reason why string compares are used is to make it work consistently across library/DLL boundaries. If you build your application statically and/or you are using certain compilers then you can probably use:
使用字符串比较的原因是使其跨库/DLL 边界一致地工作。如果您静态构建应用程序和/或使用某些编译器,那么您可能可以使用:
cout << (typeid(Base).name() == typeid(Derived).name()) << endl;
Which is not guaranteed to work (will never give a false positive, but may give false negatives) but can be up to 15 times faster. This relies on the implementation of typeid() to work in a certain way and all you are doing is comparing an internal char pointer. This is also sometimes equivalent to:
这不能保证有效(永远不会给出误报,但可能会给出误报),但最多可以快 15 倍。这依赖于 typeid() 的实现以某种方式工作,而您所做的只是比较内部字符指针。这有时也相当于:
cout << (&typeid(Base) == &typeid(Derived)) << endl;
You canhowever use a hybrid safely which will be very fast if the types match, and will be worst case for unmatched types:
但是,您可以安全地使用混合,如果类型匹配,它将非常快,并且对于不匹配的类型将是最坏的情况:
cout << ( typeid(Base).name() == typeid(Derived).name() ||
typeid(Base) == typeid(Derived) ) << endl;
To understand whether you need to optimize this you need to see how much of your time you spend getting a new packet, compared to the time it takes to process the packet. In most cases a string compare will probably not be a large overhead. (depending on your class or namespace::class name length)
要了解是否需要对此进行优化,您需要了解与处理数据包所需的时间相比,获取新数据包所花费的时间有多少。在大多数情况下,字符串比较可能不会产生很大的开销。(取决于您的类或命名空间::类名称长度)
The safest way to optimize this is to implement your own typeid as an int (or a enum Type : int ) as part of your Base class and use that to determine the type of the class, and then just use static_cast<> or reinterpret_cast<>
优化这个最安全的方法是将你自己的 typeid 实现为一个 int (或一个 enum Type : int )作为你的 Base 类的一部分,并使用它来确定类的类型,然后只使用 static_cast<> 或 reinterpret_cast< >
For me the difference is roughly 15 times on unoptimized MS VS 2005 C++ SP1.
对我来说,未优化的 MS VS 2005 C++ SP1 的差异大约是 15 倍。
回答by MSN
For a simple check, RTTI can be as cheap as a pointer comparison. For inheritance checking, it can be as expensive as a strcmp
for every type in an inheritance tree if you are dynamic_cast
-ing from the top to the bottom in one implementation out there.
对于简单的检查,RTTI 可以像指针比较一样便宜。对于继承检查,strcmp
如果您dynamic_cast
在一个实现中从上到下进行检查,它可能与继承树中的每种类型一样昂贵。
You can also reduce the overhead by not using dynamic_cast
and instead checking the type explicitly via &typeid(...)==&typeid(type). While that doesn't necessarily work for .dlls or other dynamically loaded code, it can be quite fast for things that are statically linked.
您还可以通过不使用dynamic_cast
而是通过 &typeid(...)==&typeid(type) 显式检查类型来减少开销。虽然这不一定适用于 .dll 或其他动态加载的代码,但对于静态链接的内容可能会非常快。
Although at that point it's like using a switch statement, so there you go.
虽然在这一点上它就像使用 switch 语句,所以你去了。
回答by MSN
It's always best to measure things. In the following code, under g++, the use of hand coded type identification seems to be about three times faster than RTTI. I'm sure that a more realistic hand coded implementtaion using strings instead of chars would be slower, bringing the timings close together..
衡量事物总是最好的。在下面的代码中,在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;
}
回答by Crashworks
A while ago I measured the time costs for RTTI in the specific cases of MSVC and GCC for a 3ghz PowerPC. In the tests I ran (a fairly large C++ app with a deep class tree), each dynamic_cast<>
cost between 0.8μs and 2μs, depending on whether it hit or missed.
不久前,我在 MSVC 和 GCC 的特定情况下测量了 3ghz PowerPC 的 RTTI 时间成本。在我运行的测试中(一个相当大的具有深类树的 C++ 应用程序),每个测试的dynamic_cast<>
成本在 0.8μs 到 2μs 之间,具体取决于它是命中还是未命中。
回答by Max Lybbert
So, just how expensive is RTTI?
那么,RTTI 到底有多贵?
That depends entirely on the compiler you're using. I understand that some use string comparisons, and others use real algorithms.
这完全取决于您使用的编译器。我知道有些使用字符串比较,有些使用真正的算法。
Your only hope is to write a sample program and see what your compiler does (or at least determine how much time it takes to execute a million dynamic_casts
or a million typeid
s).
你唯一的希望是编写一个示例程序,看看你的编译器做了什么(或者至少确定执行一百万dynamic_casts
或一百万typeid
秒需要多少时间)。
回答by X-Ryl669
RTTI can be cheap and doesn't necessarly need a strcmp. The compiler limits the test to perform the actual hierarchy, in reverse order. So if you have a class C that is a child of class B which is a child of class A, dynamic_cast from a A* ptr to a C* ptr imply only one pointer comparison and not two (BTW, only the vptr table pointer is compared). The test is like "if (vptr_of_obj == vptr_of_C) return (C*)obj"
RTTI 可能很便宜,而且不一定需要 strcmp。编译器将测试限制为以相反的顺序执行实际的层次结构。所以如果你有一个类 C 是类 B 的子类,它是类 A 的子类,从 A* ptr 到 C* ptr 的 dynamic_cast 意味着只有一个指针比较而不是两个(顺便说一句,只有 vptr 表指针是比较的)。测试就像“if (vptr_of_obj == vptr_of_C) return (C*)obj”
Another example, if we try to dynamic_cast from A* to B*. In that case, the compiler will check both case (obj being a C, and obj being a B) in turns. This can also be simplified to a single test (most of times), as virtual function table is a made as an aggregation, so the test resume to "if (offset_of(vptr_of_obj, B) == vptr_of_B)" with
另一个例子,如果我们尝试从 A* 到 B* 的 dynamic_cast。在这种情况下,编译器将依次检查两种情况(obj 是 C,obj 是 B)。这也可以简化为单个测试(大多数情况下),因为虚函数表是作为聚合生成的,因此测试恢复为“if (offset_of(vptr_of_obj, B) == vptr_of_B)”
offset_of = return sizeof(vptr_table) >= sizeof(vptr_of_B) ? vptr_of_new_methods_in_B : 0
offset_of = 返回 sizeof(vptr_table) >= sizeof(vptr_of_B) ?vptr_of_new_methods_in_B : 0
The memory layout of
内存布局
vptr_of_C = [ vptr_of_A | vptr_of_new_methods_in_B | vptr_of_new_methods_in_C ]
How does the compiler know of to optimize this at compile time ?
编译器如何知道在编译时优化它?
At compile time, the compiler knows the current hierarchy of objects, so it refuse to compile different type hierarchy dynamic_casting. Then it just has to handle the hierarchy depth, and add the invert amount of tests to match such depth.
在编译时,编译器知道对象的当前层次结构,因此拒绝编译不同类型层次结构的 dynamic_casting。然后它只需要处理层次深度,并添加反向测试量以匹配这样的深度。
For example, this doesn't compile:
例如,这不会编译:
void * something = [...];
// Compile time error: Can't convert from something to MyClass, no hierarchy relation
MyClass * c = dynamic_cast<MyClass*>(something);