Quote:Original post by NumsgilQuote: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.