dynamic_cast in MSVC

Started by
31 comments, last by Numsgil 15 years, 6 months ago
Quote:Original post by Numsgil
That is, I'm upcasting to a universal base class,


This is the root of all of your pain. Redesign your system so you don't do this. Either don't do this, or make everything you need to do possible with just the base class pointer.

In your design, since you need the actual derived class type (which is why you seeked out dynamic_cast), you shouldn't be storing things as a Base*, losing the type information.
Advertisement
Quote:Original post by Antheus
dynamic_cast may be convenient, but it's slow (not because of string comparison, but because of its existence in first place.


?

Quote:
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.

...

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.


Sure, you can always reorder a problem to avoid almost any C++ feature you can think of. None of it's necessary beyond what C had. Your solution has concrete instances register themselves with a manager, and listen for events. Which is certainly a fine solution. I won't argue that. My point more is that that is not the only way to do things. It might not be appropriate for every project.

Using dynamic casts to query for interface implementations is another way. The advantage being that it's easy to maintain and the engine doesn't have to know about classes on the game side that might have been created after it was. The engine just asks stupid questions like "do you know how to draw yourself?" and "do you understand the idea of position?" If you add a new interface to the engine, you don't have to add a new manager to handle that. And if you add an interface to a class, you don't have to worry about a lot of extra code to make sure it registers itself with various managers. If it inherits from an interface, the engine knows how to use it. No special tricks.

You push most of that tedious overhead down to the language level. Your game code and your engine code keep a clean interface with each other: they mostly just communicate through interfaces.

All that said, I'm not advocating a system like that in C++ because I really do understand that dynamic_cast is awfully slow. On par with several string compares, 'cause that's what it's actually doing. I'm just saying, that speed issue aside, there's nothing inherently flawed about a design that needs to use RTTI. Again, seriously, speed issue aside. If one more person brings up the point that dynamic cast is slow and that's why you shouldn't use it... I know! I never said it wasn't. It doesn't have to be, though. It's entirely possible to have a language that implements dynamic_cast on the cheap. It's like pointing to bubble sort as the reason why you should avoid sorting integers (ie: there's such a thing as a radix sort when you're dealing with integers).

Quote:Original post by RDragon1
Quote:Original post by Numsgil
That is, I'm upcasting to a universal base class,


This is the root of all of your pain. Redesign your system so you don't do this. Either don't do this, or make everything you need to do possible with just the base class pointer.

In your design, since you need the actual derived class type (which is why you seeked out dynamic_cast), you shouldn't be storing things as a Base*, losing the type information.


Maybe I don't know the type information. Maybe the type is defined in a module that is a client to mine. All I know is that I can ask it questions about what interfaces it implements. Things like "do you know how to draw yourself" and "do you understand the idea of position."
[size=2]Darwinbots - [size=2]Artificial life simulation
Quote:Original post by Numsgil
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



It's a hash-table look up rather than a cast, which isn't important in itself I admit. But a dynamic_cast says "I haven't designed my hierarchy quite right here". Nothing in the Python code says that. 99.9% of the time in python, that function is going to exist. If it doesn't it's probably a programmer error.

Quote:
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);


Well, the point I was trying to make is that dynamic_cast is ugly for a *reason* masking that reason is a very bad idea, IMHO.

Quote:
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."


Well, I was arguing against the specific points you raised in my own reference frame. I don't have anything against dynamic_cast itself - it's existence is necessary, but its use generally points to design problems, as I see it, and so having lots of dynamic_casts indicates that design should be thought about some more. Lots of programs get by without dynamic cast just fine.

But if you don't agree about the pointing-to-design-problems part, then we won't reach any resolution in this exchange and we'll have to leave it at that :)

Quote:
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... :)


Well, me neither. I'm a bit of a template nut myself, but dynamic_cast<XYZ>(lala) sticks out more than (XYZ)lala. It tells me the reason you're casting. Dynamic cast says "I couldn't get my hierarchy quite right (or someone else couldn't get theirs quite right)" so I have to do a manual type-check here. That's what I mean really by "looking ugly".
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();}


The problem here is obvious: you're using a top type.
An object manipulated through a top type is useless, you lost all of the useful information. What you should pass to SerializeToNetwork should be a const INetworkable&. Basically, what makes the most sense.

By the way, you shouldn't use pointers but references, and you should make your code const-correct (I doubt serializing an object should change its state; but to be honest I don't even understand your network/serialization relationship, those seem to be different layers to me)
Quote:Original post by loufoque
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();}


The problem here is obvious: you're using a top type.
An object manipulated through a top type is useless, you lost all of the useful information. What you should pass to SerializeToNetwork should be a const INetworkable&. Basically, what makes the most sense.


And where does that INetworkable reference come from? Either you're downcasting at some point (and need to use dynamic cast or your own RTTI if you want to be safe about it) or every interface has a list of objects that implement it. Which would mean a list for every interface, which is a pain to maintain. And you have to remember to register your classes with all the lists for the interfaces it implements. Or use some macro magic. Neither are great options. But we're forced to use them because of the performance limitations of dynamic cast.

Quote:
By the way, you shouldn't use pointers but references, and you should make your code const-correct (I doubt serializing an object should change its state; but to be honest I don't even understand your network/serialization relationship, those seem to be different layers to me)


Seriously, don't be so literally minded; my example was strictly off the cuff. My primary point was to illustrate a kind of design where you query objects to see if they know how to behave like certain abstract objects. If you got anything else from my example I make no claims to its correctness :)
[size=2]Darwinbots - [size=2]Artificial life simulation
Quote:Original post by the_edd
It's a hash-table look up rather than a cast, which isn't important in itself I admit. But a dynamic_cast says "I haven't designed my hierarchy quite right here". Nothing in the Python code says that. 99.9% of the time in python, that function is going to exist. If it doesn't it's probably a programmer error.

Well, I was arguing against the specific points you raised in my own reference frame. I don't have anything against dynamic_cast itself - it's existence is necessary, but its use generally points to design problems, as I see it, and so having lots of dynamic_casts indicates that design should be thought about some more. Lots of programs get by without dynamic cast just fine.

But if you don't agree about the pointing-to-design-problems part, then we won't reach any resolution in this exchange and we'll have to leave it at that :)


It's not that I don't agree, it's that I don't understand. Sure, it could be used very irresponsibly. But that's true of pretty much everything. Other than the case where you are essentially switch casing on its type (and therefore entirely missing the point of OO), in what way can it allow bad design? And in what ways is such a design bad? Harder to maintain? Harder to add new features to? More than one place you need to modify in code to change a single feature? (Again, speed issues aside for the moment).
[size=2]Darwinbots - [size=2]Artificial life simulation
Well, since dynamic_cast is just a fancy way of type-switching it has many of the same (maintenance) drawbacks.

If the interface you're checking for with dynamic_cast changes to another class, you must go back and look for all your dynamic_casts and change them. The compiler won't tell you. If there was a virtual function, it would work now and forever, no changes necessary. This has echoes of having to update all your switch statements when you introduce a new type in code that does type-switching.

You'll also have to advertise to all of your users that there's a special case (or many special cases if you're casting to multiple types all over the place). And your 'users' may of course simply be you a little way in to the future. The only way for them to take advantage of that is to use the rigid, hard-coded mechanism you've provided, rather than an extensible one. You've pulled the polymorphic rug out from under them.

In other words, what's wrong with a virtual function? It's usually very easy to do without dynamic_cast, particularly in the genesis of a piece of code.

These comments are generalisations, so of course your design may not have these particular problems (yet...)
Quote:Original post by the_edd
Well, since dynamic_cast is just a fancy way of type-switching it has many of the same (maintenance) drawbacks.

If the interface you're checking for with dynamic_cast changes to another class, you must go back and look for all your dynamic_casts and change them. The compiler won't tell you.


Not sure what you mean about not getting compiler errors. If you change the name of the interface, you'll get a compile error. If you change the interface's methods (rename Draw to SubmitDrawCommands or something) you'll also get a compile error when your existing code downcasts and tries to use a non existant function. The only danger is bloating classes with methods that no longer exist. And that won't matter too much if your code is using the interfaces to manipulate objects, since methods that aren't part of the interface will be hidden, so it'll be a strictly internal problem for the class.

Quote:
If there was a virtual function, it would work now and forever, no changes necessary. This has echoes of having to update all your switch statements when you introduce a new type in code that does type-switching.


I assume you mean virtual functions on a single base class or a linear tree instead of virtual functions on several interfaces? Virtual functions are fine, but they require the base class to potentially understand some high level concepts, and potentially become rather bloated. Maybe you don't want every object in your world to have a Draw method. Or even understand that there is such a thing. If you design it right, your graphics code can exist in its own module and the rest of your code can be entirely ignorant of the fact that objects can get drawn at all.

Quote:
You'll also have to advertise to all of your users that there's a special case (or many special cases if you're casting to multiple types all over the place).


What special case? I think we're talking about different designs. I don't mean something where the core code has to be updated when you add a new object type. Just when you add a whole new subsystem. This is the sort of thing I'm thinking (pseudocode):

struct IDrawable{	void Draw() = 0;};struct IDamagable{	void CheckForDeath() = 0;};struct INetworkable{	void SerializeToNetwork() = 0;};void DrawObject(Object *Ob){	IDrawable *drawable = dynamic_cast<IDrawable *>(Ob);	if(drawable)		drawable->Draw();}void NetworkLogic(Object *Ob){	INetworkable *networkable = dynamic_cast<INetworkable *>(Ob);	if(networkable)		networkable->SerializeToNetwork();}void QueryBreakObject(Object *Ob){	IDamagable *damagable = dynamic_cast<IDamagable *>(Ob);	if(damagable)		damagable->CheckForDeath();}/// Somewhere in the client code:class Crate : public IDamagable, public IDrawable, public INetworkable{	//implementation of interfaces};class ParticleEmitter : public IDrawable{	//implementation of interfaces};


Like that. Suppose you want to add something like a player. It needs to be drawable, networkable, and damagable. You can create it on the client side without ever touching your base code, because your base code will just treat it as some random object that can be networked, drawn, and broken. And the base code might be further split up in to even more discrete units.

The only real downside I can think of is that it forces you to do a lot of inheritance. It means making your physics (which needs to maintain state) a component of a class is much harder, and you'll be tempted instead to make your crates inherit from the physics object, which may or may not be good design. Maybe you treat the physics object as a separate object which your create simply queries for position and orientation data...

I definitely do not mean adding special cases for crates and particle emitters and players throughout the base code. That's very brittle design.
[size=2]Darwinbots - [size=2]Artificial life simulation
Quote:Original post by Numsgil
What special case? I think we're talking about different designs. I don't mean something where the core code has to be updated when you add a new object type. Just when you add a whole new subsystem. This is the sort of thing I'm thinking (pseudocode):

*** Source Snippet Removed ***


There's only one detail about that. You have the concrete type information available when you create the object. Then you discard that information, and use dynamic_cast to recover it - resulting in redundancy.

Quote:void NetworkLogic(Object *Ob)
{
INetworkable *networkable = dynamic_cast<INetworkable *>(Ob);
if(networkable)
networkable->SerializeToNetwork();
}


This is all fine and dandy, but it's direct consequence of discarding the type information which you already had, forcing you into performing a redundant type-cast.

Consider the case where only 3% of components are INetworkable. You will be required to blindly query every object for a random chance of it being networkable. The dynamic cast here isn't cause of performance problem.

The use case for network component will almost certainly look like this:
- When a client connects, it receives a set of all networkable objects
- When a new object is created, it's set to all clients

This can be extended to add range checks, but the complexity doesn't change.

And this led to my proposal. You *know* the exact type when you create the component, use that to your advantage.

struct INetworkable;typedef std::vector<INetworkable *> Networables;struct INetworkable{  INetworkable(Networkables & n) {    n.push_back(this); // I believe this is legit   }  void SerializeToNetwork() = 0;};
This doesn't change including hierarchies in any way whatsoever, yet removes the burden of dynamic casts altogether. Whenever a client connects, you simply iterate through your Networkables instance and invoke the interface.


But usually, component-based designs are a replacement for rigid hierarchies.

Just to give a trivial example: IControler is entity controller interface. For AI it's one implementation, for Player it's another, for everything else, it's third.

What happens next is that NPCs and Player are almost identical, yet differ in controller only. Then you end up with:
- PlayerObject : public IPlayerControler
- NPCObject : public INpcController

You will need two identical, yet completely separate classes to do the same, because they differ in minor controller implementation.

Next solution will be to implement base class for those two which will have everything that Player and NPC share minus the controller. One particular problem solves, yet the issue persists. Until at some point, you will end up with a bunch of prototype abstract classes holding various common functionality minus the specialized functionality. Such as PlayerAndNPCAndStaticObject which will be base for those 3 object types.


And this is the reason why dynamic component systems which assemble components completely during run-time gained popularity. Those systems have no hierarchy, and have single base class containing a list of all components implemented. Each component has pointer to parent and its own type id in addition to whichever functionality it defines.

This not only completely removes coupling (components can be defined in DLLs and instantiated during run-time), but also doesn't lock you into complex chains of hierarchy resulting in absurd inheritance or illogical coupling.
Quote:Original post by Numsgil
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


It differs in that you don't have to write it (assuming the else-handling should be an "error"), or in that you can easily give a meaningful name to the process (if the else-handling should be something else)

Quote: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);


Why would that need to be a macro? Something like this (untested) should do it:

template <typename Derived, typename Base>inline void attempt(Base& b, void(Derived::* func)()) {  // Note that dynamic_cast() of a reference throws an exception on failure.  ((dynamic_cast<Derived>(b)).*(func))();}try {  attempt<MyInterface>(MyPointer, MyFunction);} catch (std::bad_cast& bc) {  // ...}



Quote: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... :)


It's "ugly" in the sense of standing out more than C-style casts and taking more effort to type. This is deliberate. (It also makes you think more about what kind of casting behaviour is required.)

This topic is closed to new replies.

Advertisement