views:

128

answers:

4

I'd like to manage a bunch of objects of classes derived from a shared interface class in a common container.

To illustrate the problem, let's say I'm building a game which will contain different actors. Let's call the interface IActor and derive Enemy and Civilian from it.

Now, the idea is to have my game main loop be able to do this:

// somewhere during init
std::vector<IActor> ActorList;
Enemy EvilGuy; 
Civilian CoolGuy;
ActorList.push_back(EvilGuy);
ActorList.push_back(CoolGuy);

and

// main loop
while(!done) {
    BOOST_FOREACH(IActor CurrentActor, ActorList) {
        CurrentActor.Update();
        CurrentActor.Draw();
    }
}

... or something along those lines. This example obviously won't work but that is pretty much the reason I'm asking here.

I'd like to know: What would be the best, safest, highest-level way to manage those objects in a common heterogeneous container? I know about a variety of approaches (Boost::Any, void*, handler class with boost::shared_ptr, Boost.Pointer Container, dynamic_cast) but I can't decide which would be the way to go here.

Also I'd like to emphasize that I want to stay away as far as possible from manual memory management or nested pointers.

Help much appreciated :).

+4  A: 

My instant reaction is that you should store smart pointers in the container, and make sure the base class defines enough (pure) virtual methods that you never need to dynamic_cast back to the derived class.

Steve314
+8  A: 

To solve the problem which you have mentioned, although you are going in right direction, but you are doing it the wrong way. This is what you would need to do

  • Define a base class (which you are already doing) with virtual functions which would be overridden by derived classes Enemy and Civilian in your case.
  • You need to choose a proper container with will store your object. You have taken a std::vector<IActor> which is not a good choice because
    • Firstly when you are adding objects to the vector it is leading to object slicing. This means that only the IActor part of Enemy or Civilian is being stored instead of the whole object.
    • Secondly you need to call functions depending on the type of the object (virtual functions), which can only happen if you use pointers.

Both of the reason above point to the fact that you need to use a container which can contain pointers, something like std::vector<IActor*> . But a better choice would be to use container of smart pointers which will save you from memory management headaches. You can use any of the smart pointers depending upon your need (but not auto_ptr)

This is what your code would look like

// somewhere during init
std::vector<some_smart_ptr<IActor> > ActorList;
ActorList.push_back(some_smart_ptr(new Enemy()));
ActorList.push_back(some_smart_ptr(new Civilian()));

and

// main loop
while(!done) 
{
    BOOST_FOREACH(some_smart_ptr<IActor> CurrentActor, ActorList) 
    {
        CurrentActor->Update();
        CurrentActor->Draw();
     }
}

Which is pretty much similar to your original code except for smart pointers part

Yogesh Arora
specifically you want a smart pointer with copy semantics
jk
yup i agree with that
Yogesh Arora
I like your approach as it looks very clean for a pointers based approach. However, as others have pointed out, what I want to do is probably better done using Boost Pointer Containers as they indeed appear to be made for just what I want to achieve. I will try your approach should that fail.Or do you see any reason *not* to use Boost Pointer Containers?
Svenstaro
Even i think Boost Pointer Containers approach is superior and syntactically easier to use
Yogesh Arora
However Boost Pointer Containers may not be used in some STL algorithm http://www.boost.org/doc/libs/1_42_0/libs/ptr_container/doc/tutorial.html#algorithms
Yogesh Arora
+4  A: 

If you want the container to exclusively own the elements in it, use a Boost pointer container: they're designed for that job. Otherwise, use a container of shared_ptr<IActor> (and of course use them properly, meaning that everyone who needs to share ownership uses shared_ptr).

In both cases, make sure that the destructor of IActor is virtual.

void* requires you to do manual memory management, so that's out. Boost.Any is overkill when the types are related by inheritance - standard polymorphism does the job.

Whether you need dynamic_cast or not is an orthogonal issue - if the users of the container only need the IActor interface, and you either (a) make all the functions of the interface virtual, or else (b) use the non-virtual interface idiom, then you don't need dynamic_cast. If the users of the container know that some of the IActor objects are "really" civilians, and want to make use of things which are in the Civilian interface but not IActor, then you will need casts (or a redesign).

Steve Jessop
+2  A: 

As you have guessed you need to store the objects as pointers.
I prefer to use the boost pointer containers (rather than a normal container of smart pointers).

The reason for this is the boost ptr container access the objects as if they were objects (returning references) rather than pointers. This makes it easier to use standard functors and algorithms on the containers.

The disadvantage of smart pointers is that you are sharing ownership.
This is not what you really want. You want ownership to be in a single place (in this case the container).

boost::ptr_vector<IActor> ActorList; 
ActorList.push_back(new Enemy()); 
ActorList.push_back(new Civilian());

and

std::for_each(ActorList.begin(), 
              ActorList.end(),
              std::mem_fun_ref(&IActor::updateDraw));
Martin York
liked the way you used for_each
Yogesh Arora
Can you explain your for_each and how you'd use it using BOOST_FOREACH?
Svenstaro
std::for_each(I1,I2,Action). Applies the Action to all values (in this case calls the method updateDraw) in the range between I1 and I2 (not including I2). Where I1, I2 are iterators. See: http://www.sgi.com/tech/stl/for_each.html
Martin York
You should ask another question on how to use BOOST_FOREACH() I don't use it enough to answer off the top of my head and I don't have a compiler to play with at the moment.
Martin York