dynamic_cast in MSVC

Started by
31 comments, last by Numsgil 15 years, 7 months ago
Quote:Original post by Numsgil
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.


I mean if you keep the original class you're dynamic_cast-ing to and add a new one that should be cast to instead. But given your specific example further on, I don't think you have this problem.

But to illustrate what I meant, just to be clear, let's suppose there's a calculator application whose numbers can be either doubles or long doubles (and pretend double and long double are classes that implement a real_number interface).

In some places, for whatever reason you might want to check if a real_number* is really a double* and if so convert it to a long double* to keep accuracy in a calculation.

As soon as you introduce a new type really_long_double, things get ugly. An extra virtual function in real_number is probably the way to go, here.

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


Yes, that is indeed a down side. This is what visitor overcomes, though whether a visitor is any better is debatable (I'm really not a fan of that pattern).

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



<rant>aside: man, I *really* hate how this forum about *code* strips *code* out of quoted text. I had to paste this back in.</rant>

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


Well, if I were doing it I would keep references around to these objects with the type info intact then just do foreach(drawable, draw_it) or whatever. There's a performance/space trade-off of course with this approach.

Quote:I definitely do not mean adding special cases for crates and particle emitters and players throughout the base code. That's very brittle design.


Yep, I'm with you now. I personally would still do it differently by either keeping typed references around or creating wrapper objects that implement all the interfaces needed (often with no-ops in the overrides). But your approach does make reasonable sense, here, I agree.
Advertisement
Quote:Original post by Antheus
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.


No... I have the concrete type information available when I create the object then discard it and use dynamic cast to recover part of it. I don't need the full and actual object, just the part of it that implements a more low-level interface. The point I'm trying to make here is that maintaining separate lists for your particle emitters and crates and monsters and stuff is a pain. Even maintaining different lists for IDrawables and IDamagables is a pain, though it's a step in the right direction.

Really, you only care about maybe two lists: things that exist only in the current level, and things that live after the current level is over (your inventory and stuff). Everything else is bookkeeping.

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


Yeah, so? It saves me at least as much effort trying to maintain umpteen different interface lists. The only really negative aspect is the added performance overhead. It's a large overhead, sure, but that's more a commentary on the language and compilers than on the design. I can envision a special case where dynamic_casting to find interfaces is a O(1) operation. Probably just an array lookup.

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


You're still maintaining lots and lots of different lists. Every interface needs a list. Who is in charge of adding a new object to the Networkables list? What if someone forgets? What if an object is added twice because both its base class implements it too? If I want to create a new interface I have to make sure that there's a new list for it to use. And I need to write unit tests for it, and maintain it.

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


Why would you create a new interface for NPC and Player? It would seem more probable that you'd use the same IController interface for both, and PlayerObject and NPCObject would simply use different implementation details. If you have to create a unique interface for every object type in your game then yes, it entirely defeats the purpose. But really you shouldn't have to. The core engine can simply understand the idea of some IController interface.

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


Yes, if that's the end result you've accomplished nothing. I'm not convinced it has to be, though.

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


That's exactly what I'm talking about, just done with components instead of inheritance. And yes, if you can get away with it it's preferable. That's the core downside that I can see to using lots of interfaces, since it makes using components harder. And you can't really dynamic_cast if your classes contain components which implement interfaces instead of the class itself, since the components are invisible on the object level.

However, if you have to do something like this in the ctor of all your objects

this->RegisterModule(m_ModuleA);this->RegisterModule(m_ModuleB);this->RegisterModule(m_ModuleC);this->RegisterModule(m_ModuleD);


Then you still have a maintenance nightmare. A slightly more reasonable solution would have you pass the this pointer to your modules' ctors, but then you have to deal with certain compiler warnings, and you have to be really careful about how you use the pointer in the modules' ctor, since it's only partially constructed. Still not a perfect solution.
[size=2]Darwinbots - [size=2]Artificial life simulation
Quote:Original post by the_edd
I mean if you keep the original class you're dynamic_cast-ing to and add a new one that should be cast to instead. But given your specific example further on, I don't think you have this problem.

But to illustrate what I meant, just to be clear, let's suppose there's a calculator application whose numbers can be either doubles or long doubles (and pretend double and long double are classes that implement a real_number interface).

In some places, for whatever reason you might want to check if a real_number* is really a double* and if so convert it to a long double* to keep accuracy in a calculation.

As soon as you introduce a new type really_long_double, things get ugly. An extra virtual function in real_number is probably the way to go, here.


Yeah, that would be a case of abuse where a virtual function makes more sense.

Quote:
Well, if I were doing it I would keep references around to these objects with the type info intact then just do foreach(drawable, draw_it) or whatever. There's a performance/space trade-off of course with this approach.


That's how I'd do it too, in the real world. I'm thinking more in terms of some imaginary language where downcasting is free or almost free. It's something that a compiler could handle very easily. It's not something we as programmers should have to worry about, but we're forced to because of lousy dynamic_cast performance.

Quote:Original post by Zahlman
Why would that need to be a macro? Something like this (untested) should do it:

*** Source Snippet Removed ***


Sure, templates work, too. Probably better since they mimic the dynamic_cast syntax better.
[size=2]Darwinbots - [size=2]Artificial life simulation

This topic is closed to new replies.

Advertisement