Jump to content

  • Log In with Google      Sign In   
  • Create Account

Banner advertising on our site currently available from just $5!


1. Learn about the promo. 2. Sign up for GDNet+. 3. Set up your advert!


dynamic_cast in MSVC


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
32 replies to this topic

#1 Numsgil   Members   -  Reputation: 501

Like
0Likes
Like

Posted 18 September 2008 - 04:35 PM

Visual Studio (C++) apparently does a string compare when you call dynamic_cast, to handle edge cases of inheriting across assembly boundaries. This makes an operation that wasn't all that fast to begin with excruciatingly slow. Is there a way to tell the compiler not to do that? Let it assume that all libraries will either be static linked or not have classes inherit across assembly boundaries?
Darwinbots - Artificial life simulation

Sponsor:

#2 bgarney   Members   -  Reputation: 122

Like
0Likes
Like

Posted 18 September 2008 - 04:47 PM

It should only ever need to do pointer compares, which are quite fast. Have you profiled it? Have you looked at the assembly? Unless you can measure that it's slower, then it's not really worth worrying about...

#3 Sc4Freak   Members   -  Reputation: 643

Like
0Likes
Like

Posted 18 September 2008 - 05:04 PM

Quote:
Original post by bgarney
It should only ever need to do pointer compares, which are quite fast. Have you profiled it? Have you looked at the assembly? Unless you can measure that it's slower, then it's not really worth worrying about...

No, he's right. MSVC's dynamic_cast will perform a full string comparison between class names. If you've got a deep hierarchy, it is indeed very slow. If you see strcmp bubbling to the top in the profiler, dynamic_cast is a possible suspect.

Unfortunately, I don't believe any such compiler switch exists. The best solution, if possible, would be to eliminate the need for dynamic_cast in the first place.

#4 Numsgil   Members   -  Reputation: 501

Like
0Likes
Like

Posted 19 September 2008 - 06:38 AM

Quote:
Original post by Sc4Freak
Quote:
Original post by bgarney
It should only ever need to do pointer compares, which are quite fast. Have you profiled it? Have you looked at the assembly? Unless you can measure that it's slower, then it's not really worth worrying about...

No, he's right. MSVC's dynamic_cast will perform a full string comparison between class names. If you've got a deep hierarchy, it is indeed very slow. If you see strcmp bubbling to the top in the profiler, dynamic_cast is a possible suspect.

Unfortunately, I don't believe any such compiler switch exists. The best solution, if possible, would be to eliminate the need for dynamic_cast in the first place.


Yeah, right now it's all tucked in level initialization code, so it's not slowing the game down, but it's just a pain that it's doing that. And it limits me to only really using it during initialization. And it makes it a hard sell to other programmers who are just C casting instead. I really only use it for things like this:


class A { virtual ~A(); };
class B : public A { };

A *APointer = new B();

...

B *BPointerQuery = dynamic_cast<B *>(APointer);
if(BPointerQuery)
//...


That is, I'm upcasting to a universal base class, then downcasting back to the original type. I could implement my own RTTI system, using virtual functions, but it's a pain to maintain.

Is there a way to build my own hacky version of a RTTI by somehow fiddling with the virtual table pointer? It would only need to be MSVC specific, since I think MSVC is the only one that does it like this. And it can be much stupider than the usual dynamic_cast since I'm only interested in upcasting then downcasting back to the same type. Something like this(?):


#define HACKY_POINTER_DYNAMIC_CAST(DesiredType, Pointer)
#ifdef _MSC_VER
// hacky
#else
dynamic_cast<DesiredType *>(Pointer)
#endif


(I know there are some macro tricks I should use here that I'm not, but it's purely a quick and dirty example). Or maybe use templates so I can still use the label<Type *>(pointer) format.

[Edited by - Numsgil on September 19, 2008 1:38:22 PM]
Darwinbots - Artificial life simulation

#5 MJP   Moderators   -  Reputation: 14035

Like
0Likes
Like

Posted 19 September 2008 - 07:50 AM

Quote:
Original post by Numsgil

Is there a way to build my own hacky version of a RTTI by somehow fiddling with the virtual table pointer?



Yes but it's VERY hacky and VERY scary. It requires using implementation details of how MSVC stores vtable pointers and using some assembly to modify portions of your code. SiCrane talks about it in detail towards the end of this thread.





#6 Numsgil   Members   -  Reputation: 501

Like
0Likes
Like

Posted 19 September 2008 - 07:52 AM

Quote:
Original post by MJP
Quote:
Original post by Numsgil

Is there a way to build my own hacky version of a RTTI by somehow fiddling with the virtual table pointer?



Yes but it's VERY hacky and VERY scary. It requires using implementation details of how MSVC stores vtable pointers and using some assembly to modify portions of your code. SiCrane talks about it in detail towards the end of this thread.


Heh, that's a bit too hacky and scary :)
Darwinbots - Artificial life simulation

#7 e‍dd   Members   -  Reputation: 2109

Like
0Likes
Like

Posted 19 September 2008 - 09:27 AM

Quote:
Original post by Numsgil
Quote:
Original post by Sc4Freak
Quote:
Original post by bgarney
It should only ever need to do pointer compares, which are quite fast. Have you profiled it? Have you looked at the assembly? Unless you can measure that it's slower, then it's not really worth worrying about...

No, he's right. MSVC's dynamic_cast will perform a full string comparison between class names. If you've got a deep hierarchy, it is indeed very slow. If you see strcmp bubbling to the top in the profiler, dynamic_cast is a possible suspect.

Unfortunately, I don't believe any such compiler switch exists. The best solution, if possible, would be to eliminate the need for dynamic_cast in the first place.


Yeah, right now it's all tucked in level initialization code, so it's not slowing the game down, but it's just a pain that it's doing that. And it limits me to only really using it during initialization.


Limiting the use of dynamic_cast is the way it should be, you shouldn't be seeking to change that. An abundance of dynamic_casts (or any type of cast, really) points to design problems.

Quote:
I really only use it for things like this:


class A { virtual ~A(); };
class B : public A { };

A *APointer = new B();

...

B *BPointerQuery = dynamic_cast<B *>(APointer);
if(BPointerQuery)
//...

That is, I'm upcasting to a universal base class, then downcasting back to the original type. I could implement my own RTTI system, using virtual functions, but it's a pain to maintain.


If you're design isn't malleable enough right now to restructure things such that dynamic_cast isn't needed, then you might consider a variant of the visitor pattern.

Or you could do something even simpler by adding a virtual function to your hierarchy that does something in B's overridden implementation, but nothing by default. It's not particularly neat, but it's no worse than your casts.

Quote:
Is there a way to build my own hacky version of a RTTI by somehow fiddling with the virtual table pointer? It would only need to be MSVC specific, since I think MSVC is the only one that does it like this. And it can be much stupider than the usual dynamic_cast since I'm only interested in upcasting then downcasting back to the same type. Something like this(?):
[code=auto:0]


Don't even go there :)

IMHO, it would be better to get out of this "it's ok to use dynamic_cast a lot" mindset.

#8 Numsgil   Members   -  Reputation: 501

Like
0Likes
Like

Posted 19 September 2008 - 10:16 AM

Quote:
Original post by the_edd
Limiting the use of dynamic_cast is the way it should be, you shouldn't be seeking to change that. An abundance of dynamic_casts (or any type of cast, really) points to design problems.


That depends. Should we avoid dynamic casts because they're slow, or because they lead to bad designs? A program in another language using dynamic typing is certainly valid if that's how the language is meant to be used. Ruby for example. I haven't done much dynamic typing myself, but I recognize lots of strengths from it.

I would say you should avoid dynamic casts because they're slow, not because there's something inherently less flexible, or more risky, about a design incorporating them. Well, that and C++ is really built to be statically typed, and you're kind of twisting it to be dynamically typed. So you might code yourself in to a dead end with language limitations. But at its core I don't think there's anything evil about dynamic casting. Or I haven't seen any evidence of it anyway.
Darwinbots - Artificial life simulation

#9 loufoque   Banned   -  Reputation: 93

Like
0Likes
Like

Posted 19 September 2008 - 11:31 AM

Checking a type with if/else is always a bad design.
If you want to check different possibilities of types, you should perform an actual visitation, which will actually scale and won't allow you to forget cases.

#10 Numsgil   Members   -  Reputation: 501

Like
0Likes
Like

Posted 19 September 2008 - 11:45 AM

Quote:
Original post by loufoque
Checking a type with if/else is always a bad design.
If you want to check different possibilities of types, you should perform an actual visitation, which will actually scale and won't allow you to forget cases.


Sure if you're essentially switch casing on every possible case you're probably better off restructuring a bit. I mean more things like this:


void SerializeToNetwork(Object *Ob)
{
INetworkable *network = dynamic_cast<INetworkable *>(Ob);
if(network)
network->SendNetworkData();
}


The idea being you don't want to pollute the def of Object with networking logic, but you want to be able to catch when an object implements the Networkable interface. There's probably other ways of doing this, but what's inherently bad about a design like this? (I don't mean this rhetorically, I'm honestly asking).

Note, this isn't actually the case I have in my code right now, this is just a hypothetical example.
Darwinbots - Artificial life simulation

#11 e‍dd   Members   -  Reputation: 2109

Like
0Likes
Like

Posted 19 September 2008 - 12:36 PM

Quote:
Original post by Numsgil
Quote:
Original post by the_edd
Limiting the use of dynamic_cast is the way it should be, you shouldn't be seeking to change that. An abundance of dynamic_casts (or any type of cast, really) points to design problems.


That depends. Should we avoid dynamic casts because they're slow,

Yes.

Quote:
or because they lead to bad designs?

And double yes :)

Quote:
A program in another language using dynamic typing is certainly valid if that's how the language is meant to be used.


These languages don't use dynamic casts. They simply use polymorphism. Perhaps the implementation of the language interpreter has type-switching or something functionally equivalent to a dynamic_cast internally, but this is hidden inside there because it's a bad idea to put it into user code.

For example in Python when I call a lambda or a function or an instance of a class that has a __call__ method, the interpreter doesn't dynamic_cast the object I'm calling to a function. It simply asks the object if it's callable.

Quote:
Ruby for example. I haven't done much dynamic typing myself, but I recognize lots of strengths from it.


Dynamically typing is certainly nice for a lot of things, but it has nothing inherently to do with dynamic casts. It's just a higher level of polymorphism.

Quote:
I would say you should avoid dynamic casts because they're slow, not because there's something inherently less flexible, or more risky, about a design incorporating them. Well, that and C++ is really built to be statically typed, and you're kind of twisting it to be dynamically typed. So you might code yourself in to a dead end with language limitations. But at its core I don't think there's anything evil about dynamic casting. Or I haven't seen any evidence of it anyway.


Well I would say, as your example shows, that you could redesign your hierarchy to avoid it i.e. you're not using the tools for polymorphism that the statically typed language provides, C++ in this case.

But as you point out, this isn't always easy and sometimes your program yourself in to a corner because runtime polymorphism in languages such as C++ is only really doable via inheritance. Since inheritance is a very strong form of coupling, this polymorphic behaviour is hard to change and you might end up having to do a dynamic_cast here or there. But this is the exception -- it's there because you've got yourself stuck. It's not a means to avoid getting stuck in the first place. Otherwise the syntax would be much nicer; the C++ cast syntax is designed to look ugly for a reason.

#12 Numsgil   Members   -  Reputation: 501

Like
0Likes
Like

Posted 19 September 2008 - 01:58 PM

Quote:
Original post by the_edd
For example in Python when I call a lambda or a function or an instance of a class that has a __call__ method, the interpreter doesn't dynamic_cast the object I'm calling to a function. It simply asks the object if it's callable.


How is that different to the end user to something like this (aside from implementation details of dynamic_cast):


ISomeInterface *pointer = dynamic_cast<ISomeInterface *>(MyObject);
if(pointer)
pointer->SomeFunction();
else
//some error, or ignore


Other than that some languages allow duck typing so you don't even need to know the name of your interface. You can just use the name of the function. You can even collapse that code above in to a macro if you're adverse to things that you consider ugly :) Then it'd just be ATTEMPT_ACTION(MyPointer, MyInterface, MyFunction);

Your post seems to say not to use dynamic cast because it's dynamic cast, and you're not supposed to use that. That is, the argument sounded cyclical. "These languages don't use dynamic_cast, they just use something that is exactly the same but not called that."

I also don't find template notation as ugly as most people seem to find it. Angle brackets don't make me immediately wet myself. Writing templates, on the other hand... :)
Darwinbots - Artificial life simulation

#13 Spoonbender   Members   -  Reputation: 1254

Like
0Likes
Like

Posted 19 September 2008 - 02:16 PM

Quote:
Original post by NumsgilYour post seems to say not to use dynamic cast because it's dynamic cast, and you're not supposed to use that. That is, the argument sounded cyclical. "These languages don't use dynamic_cast, they just use something that is exactly the same but not called that."

Another way to look at is to say that *explicit* dynamic casts are the problem. Of course something similar happens under the hood in Python or Ruby, but the programmer doesn't perform the cast explicitly.
What the language does internally doesn't really count. You could say that internally, C/C++ uses goto's all over the place (because it compiles to assembly, where goto is standard practice), but that doesn't mean you, the programmer, should be writing goto's all over the place.

By the way, something that puzzles me a bit:
Quote:
And it makes it a hard sell to other programmers who are just C casting instead

How does that work? If people can get away with using a plain C cast instead, it seems like your dynamic casts aren't necessary. And if they aren't necessary, why are you using them?

Unless I misunderstand you, it seems like you're using dynamic casts in cases where you could get away with a static_cast.


Also have you looked into boost.variant or boost.any? If you just need a way to store a bunch of objects in a single container, using one of those instead might solve your problem.

#14 phantom   Moderators   -  Reputation: 8707

Like
0Likes
Like

Posted 19 September 2008 - 02:28 PM

Quote:
Original post by Spoonbender
By the way, something that puzzles me a bit:
Quote:
And it makes it a hard sell to other programmers who are just C casting instead

How does that work? If people can get away with using a plain C cast instead, it seems like your dynamic casts aren't necessary. And if they aren't necessary, why are you using them?

Unless I misunderstand you, it seems like you're using dynamic casts in cases where you could get away with a static_cast.


The main difference being a C cast doesn't check, it's basically you saying 'yeah, trust me on this, it'll work'. The C++ casts on the other hand are more explicate in what they are doing AND in the case of dynamic_cast do a runtime check.

It's the age old story of programming; safety at the cost of runtime speed.
C casts are "fast" because they don't check, they are also more likely to asplode in your face if you ever feed it something wrong. However, while it works it's a hard sell to say to people 'look, use the safe version' because they'll just turn around and say 'but it's slow and we'll never make that mistake..' (insert sitcom laugh track here).



#15 Hodgman   Moderators   -  Reputation: 40127

Like
0Likes
Like

Posted 19 September 2008 - 02:53 PM

Quote:
Original post by Numsgil:
void SerializeToNetwork(Object *Ob)
{
INetworkable *network = dynamic_cast<INetworkable *>(Ob);
if(network)
network->SendNetworkData();
}
In my engine, I would write it like this:
void SerializeToNetwork(Object *Ob)
{
INetworkable *network = Ob->GetInterface<INetworkable>();
if(network)
network->SendNetworkData();
}

GetInterface looks like this:
template<class T>
T* Object::GetInterface()
{
InterfaceMap::iterator itInterface = m_Interfaces.find( GetTypeInfo<T>() );
if( itInterface != m_Interfaces.end() )
{
return static_cast<T*>( itInterface->second );
}
return NULL;
}

N.B. m_Interfaces is a std::map<TypeInfo,void*>
In the objects constructor, it populates m_Interfaces by doing static_casts to each interface type that it supports.
e.g. m_Interfaces[GetTypeInfo<INetworkable>()] = static_cast<INetworkable*>( this );

TypeInfo and GetTypeInfo are my wrapper for C++'s native RTTI. On compilers that have fast RTTI I just let the compiler do it, but on MSVC I try and optimize type comparisons:
	#if the compiler has fast RTTI
class TypeInfo
{
public:
TypeInfo() : t(0) {}
TypeInfo( const std::type_info& t ) : t(&t) {}
TypeInfo( const TypeInfo& t ) : t(t.t) {}
bool operator==(const TypeInfo& rhs) const { return t == rhs.t || (t && rhs.t && *t == *rhs.t); }
bool operator!=(const TypeInfo& rhs) const { return !(*this == rhs); }
private:
const std::type_info* t;
};
template<class T>
inline TypeInfo GetTypeInfo() { return typeid( T ); }
#else
typedef uint32 TypeInfo;
template<class T>
inline TypeInfo GetTypeInfo( ) { static uint32 hashT = hash::Fnv32a(typeid(T).name()); return hashT; }
#endif

This does run the risk of hash-collisions though, so the next thing I'm going to do with this code is check for collisions in debug builds...

Also this system bloats the size of your objects because the interface map is stored per-object, not per-class (but you can use that to have objects choose whether they expose an interface or not at runtime, if needed - e.g. I use this interface system for inter-entity communication in my component system which require runtime-composition of classes).

#16 Numsgil   Members   -  Reputation: 501

Like
0Likes
Like

Posted 19 September 2008 - 03:05 PM

@phantom: yes, that's my thinking. We could get away with static casting but that puts a lot of faith in a system that was hastily built to meet a milestone goal. It'd be far better if it were something we could leverage from the language so we knew, period, that this is not a potential source for bugs.

Quote:
Original post by Hodgman
Quote:
Original post by Numsgil:
void SerializeToNetwork(Object *Ob)
{
INetworkable *network = dynamic_cast<INetworkable *>(Ob);
if(network)
network->SendNetworkData();
}
In my engine, I would write it like this:*** Source Snippet Removed ***
GetInterface looks like this:*** Source Snippet Removed ***
N.B. m_Interfaces is a std::map<TypeInfo,void*>
In the objects constructor, it populates m_Interfaces by doing static_casts to each interface type that it supports.
e.g. m_Interfaces[GetTypeInfo<INetworkable>()] = static_cast<INetworkable*>( this );

TypeInfo and GetTypeInfo are my wrapper for C++'s native RTTI. On compilers that have fast RTTI I just let the compiler do it, but on MSVC I try and optimize type comparisons:*** Source Snippet Removed ***
This does run the risk of hash-collisions though, so the next thing I'm going to do with this code is check for collisions in debug builds...

Also this system bloats the size of your objects because the interface map is stored per-object, not per-class (but you can use that to have objects choose whether they expose an interface or not at runtime, if needed).


Yeah, I had thought along those lines. Essentially you invent your own RTTI system that's more efficient than the built in one. Which is cool and good when the built in one is slow (ala MSVC). It's just a pain because it's so brittle to overworked programmers forgetting to add an interface. Every new class has to do something in it's ctor, and it's easy to forget. Every new interface you add needs to add something to the ctor and that's easy to forget, too. And heaven forbid if you remove and interface and forget to modify the ctor.

It just seems like it'd make more sense to let the language handle it, if it weren't so dang blasted terrible at doing it efficiently.
Darwinbots - Artificial life simulation

#17 Hodgman   Moderators   -  Reputation: 40127

Like
0Likes
Like

Posted 19 September 2008 - 06:55 PM

Quote:
Original post by Numsgil
It's just a pain because it's so brittle to overworked programmers forgetting to add an interface. Every new class has to do something in it's ctor, and it's easy to forget. Every new interface you add needs to add something to the ctor and that's easy to forget, too. And heaven forbid if you remove and interface and forget to modify the ctor.
Yeah, remembering to register interfaces is a problem with my system, but if you remove an interface and are still trying to register it then your code wont compile (invalid static_cast).

The engine I use at work also implements it's own RTTI using magic macros that just have to be put into the class declaration. If you forget to add the macros you'll also get compiler errors to remind you, so it's easy to maintain. The downside of this particular system is that it doesn't allow for multiple inheritance, which is useful in the case of exposing interfaces, but might not be required in your case.


#18 iMalc   Crossbones+   -  Reputation: 2404

Like
0Likes
Like

Posted 19 September 2008 - 09:58 PM

Quote:
Original post by Hodgman
In my engine, I would write it like this
void SerializeToNetwork(Object *Ob)
{
INetworkable *network = Ob->GetInterface<INetworkable>();
if(network)
network->SendNetworkData();
}
Much like the COM approach:
void SerializeToNetwork(IUnknown *Ob)
{
CComQIPtr<INetworkable> network = Ob;
if (network)
network->SendNetworkData();
}


#19 Antheus   Members   -  Reputation: 2405

Like
0Likes
Like

Posted 20 September 2008 - 02:54 AM

This approach to components always left a bad taste in my mouth, mostly because of high access cost to individual components.

dynamic_cast may be convenient, but it's slow (not because of string comparison, but because of its existence in first place.


Establishing use cases first may help eliminate the penalty altogether.

For network serialization, the ad-hoc transmission typically isn't needed. When an object is serialized to a certain client, it will need to be updated as well. This means that once sent, client subscribes to changes.

The approach then involves several components:
- When client is first connected, it is made aware of all the objects in range.
void onSubscribe(Client * c) {
Point3D p = c->getPosition();
for (every e in world->serializationManager->entities) {
if (e->inRange(p) e->subscribe©;
}
};

- When a component subscribes it sends the state using internal routines:

void Serializable::onCreate(World * world) {
world->addOnTickListener(&Serializable::OnTick, this);
world->serializationManager.add(this);
}
void Serializable::onSubscribe(Client *c) {
subscribers->add©;
Message msg = packInitialState();
c->send(msg);
};
void Serializable::OnTick() {
Message msg = packDeltaState();
for (every c in subscribers) {
c->send(msg);
}
};

....
// GenericComponentManager<Serializable> World::serializationManager;



This has two advantages. First, whenever a serialization component (or any component for that matter) is added, it embeds itself world. Serialization component knows what and why it exists, and world can keep track of it without knowing which entity it belongs to.

If you look above, you'll see that no dynamic casts are needed anywhere, since we have inverted to flow control. Component becomes self-aware, and registers on construction.

The above can also be implemented once, or extended, in later case, there is no extra work to be done with regard to registration, perhaps just implementing virtual packXXX() functions.


I find that whenever you need to query some objects for some OO concept, the design should be changed to avoid it. "If (x instanceof Y)" indicates a problem which is typically solvable without query and without cast.

The above approach retains the ad-hoc run-time object composability, but has O(1) access time to components, and a guaranteed hit rate when trying to use a certain part of certain entity with very low (negligble) access cost. Compared to O(logn) each time you try to use a component plus a high cost (dynamic_cast) on every access to component.

YMMV.

#20 SiCrane   Moderators   -  Reputation: 10731

Like
0Likes
Like

Posted 20 September 2008 - 11:04 AM

Quote:
Original post by Numsgil
Quote:
Original post by MJP
Quote:
Original post by Numsgil
Is there a way to build my own hacky version of a RTTI by somehow fiddling with the virtual table pointer?

Yes but it's VERY hacky and VERY scary. It requires using implementation details of how MSVC stores vtable pointers and using some assembly to modify portions of your code. SiCrane talks about it in detail towards the end of this thread.

Heh, that's a bit too hacky and scary :)

Actually, that was just for implementing your own typeid. Implementing dynamic_cast is even more hacky and scary.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS