Skip to main content
gamedev.net
🔒 Locked

Avoiding Downcasts

Started by Ashkan Feb 25, 2008 at 7:48 AM 22 replies 4k views
Original Post
Ashkan
Ashkan
Downcasts are nasty and I would like to avoid them as much as possible, but there are times that they just seem to be unavoidable. I have this situation that I am yet to find a desirable solution to, so here I am posting this hoping to get a feedback from you fellow gamedevers. Here's an example:

class Base {
public:
    virtual Base* DoComputation() { 
         // do computations
         // return a pointer to a new instance of **Base**
    }
};

class Derived : public Base {
public:
    virtual Base* DoComputation() {
        // do computations
        // return a pointer to a new instance of **Derived**, which will be
        // upcasted to Base
    }

    void DummyFunction() {}
};

Base* pDerived = new Derived;
Base* pResultOfComputation = pDerived->DoComputation();

static_cast< Derived* >(pResultOfComputation)->DummyFunction();


So we clearly need a downcast to be able to call DummyFunction() which is a pain in the rear end, but interestingly, C++ allows for function overrides to have different return values if and only if the return value of the overridden member function in the derived class is a subtype of the return value of the member function in the base class. So the following code is 100% valid and can be used to alleviate this problem:

class Base {
public:
    virtual Base* DoComputation() { 
         // do computations
         // return a pointer to a new instance of **Base**
    }
};

class Derived : public Base {
public:
    virtual Derived* DoComputation() {
        // do computations
        // return a pointer to a new instance of **Derived**, which will be
        // upcasted to Base
    }

    void DummyFunction() {}
};

Base* pDerived = new Derived;
Derived* pResultOfComputation = pDerived->DoComputation();

pResultOfComputation->DummyFunction();


The problem though is that this approach works as long as member functions return raw pointers or references, but it clearly fails when it comes to shared_ptrs, since shared_ptr< B > is not a subtype of shared_ptr< A > even if we somehow manage to return a reference to this shared_ptr, let alone the case in which the shared_ptr is returned by value. Of course there's always the possibility of switching to CRTP, which I usually try to avoid since a template by nature is meaningless without its arguments and this tends to take away all the advantages of runtime polymorphism. At the end of the day, it seems that a downcast is really unavoidable. I was wondering if there's any known workarounds, or possibly some boost magic to kick the downcast out of the happy lands of OOP? Thanks
stonemetal
stonemetal
according to the documentation:
shared_ptr can be implicitly converted to shared_ptr whenever T* can be implicitly converted to U*


Which says to me that it should work automatically if you stay in shared pointer land. Edit: Epp! board code has added odd underlines.
Ashkan
Ashkan
Yes, you're correct by saying that shared_ptr< T > can be implicitly converted to shared_ptr< U > whenever T* can be implicitly converted to U*, but the problem is that you won't be able to override a virtual function on the return value. i.e.

class A;typedef boost::shared_ptr< A > APtr;class B;typedef boost::shared_ptr< B > BPtr;class A {public:	virtual APtr CreateInstance()  = 0;};class B : public A {public:	virtual BPtr CreateInstance() { return BPtr( new B ); }};


results in the following compiler error:
error C2555: 'B::CreateInstance': overriding virtual function return type differs and is not covariant from 'A::CreateInstance'
ToohrVyk
ToohrVyk
C++ has a dim-witted notion of type covariance. I'm afraid you cannot use covariance in this case, and will have to keep a single function signature across all classes.

You may, of course, implement the derived class function in terms of a covariant function with a different name.
Ashkan
Ashkan
Quote:

You may, of course, implement the derived class function in terms of a covariant function with a different name.


Yeah, I think that's my best bet. Thanks.
Rydinare
Rydinare
I'm not sure if this is the better solution, but I sometimes avoid downcasts by preferring cross-casts over downcasts. The reasoning behind this is that you shouldn't inherit implementation, but generally inheriting interface is a good idea. For example, In the case of your Derived class, I might have another Base class called something like "IDummy" (the I for interface). Then, the Derived class derives from that and implements it. When I want to use the "DummyFunction" method, I cast over to the IDummy interface and call it that way.


class Base {public:    virtual Base* DoComputation() {          // do computations         // return a pointer to a new instance of **Base**    }};class IDummy{    virtual void DummyFunction() = 0;};class Derived : public Base {public:    virtual Base* DoComputation() {        // do computations        // return a pointer to a new instance of **Derived**, which will be        // upcasted to Base    }    void DummyFunction() {}};Base* pDerived = new Derived;Base* pResultOfComputation = pDerived->DoComputation();dynamic_cast< IDummy* >(pResultOfComputation)->DummyFunction();


I always strive to go for as close to cast-free code as possible, but because of some limitations in C++, casts seem like a necessary evil at times.
Ashkan
Ashkan
Don't you need multiple inheritance to implement that? Is that a typo or have you left it out on purpose?

Seems like a viable option but IMHO this would result in an excessive number of tiny interfaces which tend to confuse the client programmers, besides the performance hit caused by the multiple inheritance. What do you think?
Antheus
Antheus
Quote:
At the end of the day, it seems that a downcast is really unavoidable. I was wondering if there's any known workarounds, or possibly some boost magic to kick the downcast out of the happy lands of OOP
.

Yes, because you're breaking OOP in this way.

If you need polymorphic behavior, define dummyfunction as virtual in base class. There are very rare cases where such casts don't indicate unsuitable design.

Why are you calling dummyfunction anyway?
Rydinare
Rydinare
Quote:
Original post by Ashkan
Don't you need multiple inheritance to implement that? Is that a typo or have you left it out on purpose?

Seems like a viable option but IMHO this would result in an excessive number of tiny interfaces which tend to confuse the client programmers, besides the performance hit caused by the multiple inheritance. What do you think?


Er, yes, sorry, a typo made in haste.

class Base {public:    virtual Base* DoComputation() {          // do computations         // return a pointer to a new instance of **Base**    }};class IDummy{    virtual void DummyFunction() = 0;};class Derived : public Base, public IDummy {public:    virtual Base* DoComputation() {        // do computations        // return a pointer to a new instance of **Derived**, which will be        // upcasted to Base    }    virtual void DummyFunction() {}};Base* pDerived = new Derived;Base* pResultOfComputation = pDerived->DoComputation();dynamic_cast< IDummy* >(pResultOfComputation)->DummyFunction();


There is an associated performance hit, but the performance would be in the fact that a dynamic_cast<> is significantly slower than a static_cast<>. I don't think it would confuse client programmers (you could always add comments to explain the reasoning). Ultimately, I'm in the same boat as you, that I'd like to completely avoid casting. It does seem like a necessary evil at times, though.
stonemetal
stonemetal
I see what you mean. I as has been said by others I don't think there is a solution to that.

In your first example the type you are down casting is base* and as such it will always require the cast.

Quote:

Base* pDerived = new Derived;
Base* pResultOfComputation = pDerived->DoComputation();

static_cast< Derived* >(pResultOfComputation)->DummyFunction();

The declared type of pResultOfComputation is Base* it will always need a cast to call dummyfunction no matter what is returned by docomputation. If you add a docompderived to the drived class the fact that you are using a Base* means you will have to downcast to call it as well. So your kind of out of luck either way. You will always need the cast. The quickest way to get rid of the cast is push dummy function up a level though that is usually an ugly hack.
Ashkan
Ashkan
Quote:
Original post by Antheus
Quote:
At the end of the day, it seems that a downcast is really unavoidable. I was wondering if there's any known workarounds, or possibly some boost magic to kick the downcast out of the happy lands of OOP
.

Yes, because you're breaking OOP in this way.

If you need polymorphic behavior, define dummyfunction as virtual in base class. There are very rare cases where such casts don't indicate unsuitable design.

Why are you calling dummyfunction anyway?


That was merely an example. I gave it more thought and here's a more tangible one I came up with, which will hopefully help to clarify my point.

Let's assume we have an interface called IResource and we want all implementors of this class to override a member function called Clone.

class IResource;typedef boost::shared_ptr< IResource > IResoucePtr;class IResource {public:    virtual IResourcePtr Clone() const = 0;};class IMesh : public IResource {    virtual void Render() = 0;};typedef boost::shared_ptr< IMesh > IMeshPtr;class Mesh : public IMesh {public:    virtual IResourcePtr Clone() const {       // for a moment let's say this naive line of code clones this mesh       return IMeshPtr( new Mesh );     }    virtual void Render() { // render the mesh }}IMeshPtr pMesh( LoadMesh() );IResourcePtr pClonedMesh( pMesh->Clone() );


Now how should we render the cloned mesh without a downcast? Not all resources are renderable, now are they? So we can't push the Render function up the hierarchy.

/EDIT: fixed a typo
Antheus
Antheus
Quote:
Now how should we render the cloned mesh without a downcast? Not all resources are renderable, now are they? So we can't push the Render function up the hierarchy.


Interface design de-couples interface from implementation. When you have an IResource, that's all you have. When you have IMesh, you have IMesh and IResource.

It is also impossible to clone interfaces. Since interface by itself doesn't have an implementation, there's nothing to clone. This isn't an implementation problem, it's possible to get around it, but it's a severe conceptual problem.

Assume that you have Complex object, which defines IMesh, ISkeleton and IRenderable interfaces.

Where would the pointers to interfaces point to? Same instance? Different instance?

To further complicate things, let's say you have IMesh and IRenderable pointers. Will they point to different objects? Or the same? Why can't this question be answered?

This type of problem is addressed in component/container type of models.

You have single base type: Entity.

When you need to test if an entity provides a certain interface, you do something like this:
template < class T >boost::shared_ptr<T> Entity::getComponent(){  return boost::shared_ptr<T>(dynamic_cast<T*>, this);};


Since all objects in your system are Entity, whenever you need a facet of an object, you request interface to it:boost::shared_ptr<Entity> entity;
entity.getComnponent<IRenderable>();

What does this gain you?

You have concrete implementation, which is memory managed. If, at some point you desire it, you can clone it in entirety.

At the same time, you can request a partial view into this entity, and if it doesn't support this view, the resulting pointer is NULL.

The problem to solve here is with memory management of components. I haven't tested this, but perhaps it works. Given:
<!--STARTSCRIPT--><!--source lang="cpp"-->
struct IBase {};struct IDerived : public IBase {}struct Derived : public IDerived {}Derived * d;boost::shared_ptr<IDerived> ptr_d(d);boost::shared_ptr<IBase> ptr_b(d);
If this compiles, and results in same pointer, and increments the same use count, then you can use this approach directly in above example.

The overhead of above example is one-time cost of obtaining the interface pointer and dynamic_cast.


But long story short - you have a considerable design problem due to memory management, which doesn't work in the way you want it. The compiler is quite right in not allowing you to do what you want.
stonemetal
stonemetal
Quote:
Original post by Ashkan

Now how should we render the cloned mesh without a downcast? Not all resources are renderable, now are they? So we can't push the Render function up the hierarchy.

/EDIT: fixed a typo


Well then why not make the renderer track a subclass of resources that are render able resources? You shouldn't dump everything in a generic object list then force the renderer to dig through them all to find the render able ones. I mean really when you are looking for your car keys you don't take everything in your house and check each individual item to see if it is your car keys, you go to your key ring and look through the keys for your car keys.
Ashkan
Ashkan
Seems interesting but I don't quite get where you're heading with that. First of all I don't agree with your assertion regarding the cloneability of interfaces. Clone is merely an example of a member function defined in an interface that returns a pointer to a base class that needs to be downcasted afterwards; a member function nonetheless, that is to be implemented by all implementors of that interface and that's pretty much it. What about the Cloneable interface in Java? It returns a reference to Object, which again needs to be downcasated to some desired type to be of any real use. Java designers certainly didn't mean to convey this message that the interface itself is cloneable. All they meant was that every class that implements this interface should implement a clone() method.

I quote:
Quote:

A class implements the Cloneable interface to indicate to the clone method that it is legal for that method to make a field-for-field copy of instances of that class.


Secondly, regarding the following piece of code:
boost::shared_ptr<T>(dynamic_cast<T*>, this);


I get the big picture here. If I'm not mistaken I assume you're heading for something like the QueryInterface() approach of COM, but the closest shared_ptr constructor to the one you've used above is:
template< class Y, class D >shared_ptr( Y * p, D d );


with d being a functor used to destruct p by calling d(p). Is that what you mean by passing "this" as the second argument?! Doesn't make much sense to me.

Thirdly, I don't quite understand how the given example in my previous post can cause memory management issues. You gave this example,
struct IBase {};struct IDerived : public IBase {}struct Derived : public IDerived {}Derived * d;boost::shared_ptr<IDerived> ptr_d(d);boost::shared_ptr<IBase> ptr_b(d);

in an effort to address those memory management issues (which unfortunately I don't quite get) but this has its own share of (may I say huge) memory management issues. It's a very bad idea to convert raw pointers to shared_ptrs this way, since as soon as those two shared_ptr objects go out of scope d would get destructed *twice* which results in memory corruption and termination of program at best. The only safe place to construct a shared_ptr from a raw pointer is right after the point where the object is created. Needless to say that all subsequent shared_ptrs must be constructed from the initial shared_ptr in order to give them a chance to increment the use count and not the raw pointer that points to the object. Even better, there shouldn't be a raw pointer in the first place.

While I do appreciate the time and effort you put forth in describing a better approach, I'm still somehow in dark regarding the method you seem to suggest. Maybe a more complete example help to clarify these points.

Thanks a lot.
Ashkan
Ashkan
Quote:
Original post by stonemetal
Quote:
Original post by Ashkan

Now how should we render the cloned mesh without a downcast? Not all resources are renderable, now are they? So we can't push the Render function up the hierarchy.

/EDIT: fixed a typo


Well then why not make the renderer track a subclass of resources that are render able resources? You shouldn't dump everything in a generic object list then force the renderer to dig through them all to find the render able ones. I mean really when you are looking for your car keys you don't take everything in your house and check each individual item to see if it is your car keys, you go to your key ring and look through the keys for your car keys.


Oh man, I don't know why I can't convey the message no matter how many examples I try. [smile]

As a matter of fact that's not the code that I'm using and I'm completely aware of the point you're making here. It was just an example and an example is all it was (to give it some Shakespeare-ish flavor [wink]) . You can substitute Render() with any method you want. What about ComputeBoundingVolume()? Should we have another interface for that? I mean how far can we push this game?

All I meant is that every subclass of a given class (or interface) will add something to the mix. That's part of the reason they are a more specific type, and to use those specific functions, one should downcast in situations described by the previous examples.

I hope I've made it clear.[smile]
Thanks for your attention.
stonemetal
stonemetal
Quote:
Original post by Ashkan

Oh man, I don't know why I can't convey the message no matter how many examples I try. [smile]

You can substitute Render() with any method you want. What about ComputeBoundingVolume()? Should we have another interface for that? I mean how far can we push this game?
Thanks for your attention.


Of course you wouldn't create an interface for each function you want to call, you just need one for each way an object can be interpreted. In your program you have a few major systems, the renderer, the model(as in game state not as in 3dsmax), the controller(as in MVC), or some variation there in. Each of these will need an interface to interact with an Entity in an appropriate manner. Then the renderer will hold a list of renderable entities it will never cast an object because it was given an object that provides a complete interface for it to work with. The model will have its own list of objects that provide a complete interface(and more than likely completely different from the renderer's interface) for it to work with so it should never cast either. Now you may cast once when you hand the object to the renderer but that will more than likely happen implicitly.
Antheus
Antheus
Quote:
If I'm not mistaken I assume you're heading for something like the QueryInterface() approach of COM, but the closest shared_ptr constructor to the one you've used above is:


bleh...

boost::shared_ptr<T>( dynamic_cast<T*>(this) );


Quote:
What about the Cloneable interface in Java?


Clonable is so broken, people have wanted to have it removed since it was introduced. It's considered one of the worst and most useless aspects of Java. Fortunately, there's rarely justified need to use it. I think that recently the whole Clonable/Comparable/hashcode/equals functionality is simply generated by apache libraries, and swept under a rug. It's a big mess, and considered the same type of failure as generics. Broken by design.

But - Java is very forgiving, and GC eliminates whole domains of memory management problems that you'd encounter otherwise. It also provides complete, and considerably faster type checking than C++ does.

And, from practical perspective, all objects in Java can be considered non-copyable - there really isn't any need for it.

Quote:
I'm still somehow in dark regarding the method you seem to suggest.


What you want is component-based approach. You've encountered the old set of problems with inheritance. You're dealing with improper abstraction, or better yet, conflicting abstractions, one which can never be properly solved (Python does a better job at that than all C++ family languages, including C# and Java).

A somewhat popular solution to this is property/component/container design. Unfortunately, the game-related links for this are elusive (unless you own Game Programming Gems, which has at least 2 or 3 separate examples of such design).

I know what you're trying to do, and why, but you're stretching the OO model too far, into roles it wasn't designed to do.

Maybe someone else knows other examples, or perhaps someone has the link to that Tony Hawk's game series which transitioned from inheritance to components due to complexity and interdependence.
ChaosEngine
ChaosEngine
Quote:
Original post by Antheus


Maybe someone else knows other examples, or perhaps someone has the link to that Tony Hawk's game series which transitioned from inheritance to components due to complexity and interdependence.


I could be wrong, but I believe Half-life 2 uses this kind of model. You can get the Source SDK from steam for free.

Disclaimer: it's about 3 years since I was playing with that source so my memory may be fuzzy...
if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight
Ashkan
Ashkan
@stonemetal:

Yes, we're on the same page. I know what you're talking about. It's not like you need a downcast with every single use of an interface. This is a rare case. I mean there comes a time, like the clone() method of Object in Java or just the examples I gave above, where an interface returns a pointer or a reference to another interface or object that's too up the hierarchy, and this causes you to loose some type info unless you downcast to the type you've been using prior to that call, or the another type you have in mind.

Take the clone() method of Java for instance. In Java, every class implicitly inherits from a class called Object. This Object class, has a method called clone() that returns a reference to another Object class, only because Object is too up the hierarchy. It has no knowledge of other classes whatsoever. You can override this clone() method to provide appropriate cloning functionality for the classes you're designing, but no matter what you do, the overridden clone() method would upcast the returned reference to Object. Now what are you supposed to do except to downcast the returned reference once more to make some use of it? It's not like the designers of Java were careless. Maybe it's some inherent flaw of OOP, I don't know. I was asking others to show me a workaround if one exists. Now, how do you solve this problem? Where would you put clone() in this example? What approach would you choose to alleviate this problem?

/EDIT: It seems I was a little late when posting this reply, I didn't see Antheus answer.
chairthrower
chairthrower
Just as a point of syntax dynamic_pointer_cast<>() offers better syntax and will correctly transmit the underlying reference count around which i dont think happens correctly if you downcast a raw pointer extracted using shared_ptr<>.get() with dynamic_cast<> and then assign the downcast pointer to another shared pointer with the type of the downcast. It works with weak_ref as well which is nice.

//examples using boostshared_ptr< A>  a( new B ) ;     shared_ptr< B>  sb = dynamic_pointer_cast< B>( a);    weak_ptr< A>    wa = dynamic_pointer_cast< A>( a);weak_ptr< B>    wb = dynamic_pointer_cast< B>( a);

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.