dynamic_cast in MSVC

Started by
31 comments, last by Numsgil 15 years, 6 months ago
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.
Advertisement
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... :)
[size=2]Darwinbots - [size=2]Artificial life simulation
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.
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).

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).
@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.
[size=2]Darwinbots - [size=2]Artificial life simulation
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.
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();}
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
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(c);  }};

- 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(c);  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.
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.

This topic is closed to new replies.

Advertisement