What would be the safest way to store class objects derived from a common interface in a shared container?

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

To illustrate the problem, let's say I create a game in which there will be different actors. Call the IActor interface and get Enemy and Civilian out of it.

Now the idea is for my main game loop 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 like that. This example obviously won't work, but that is pretty much the reason I'm asking here.

I would like to know: what would be the best, safest and highest level of control for these objects in a common heterogeneous container? I know about various approaches (Boost :: Any, void *, the handler class with boost :: shared_ptr, Boost.Pointer Container, dynamic_cast), but I can’t decide what the path to be here.

I would also like to emphasize that I want to stay away from manual memory management or nested pointers.

Help evaluate :).

+7
c ++ interface containers heterogeneous
source share
4 answers

As you might have guessed, you need to save the objects as pointers.
I prefer using containers with a boost pointer (rather than a regular smart pointer container).

The reason for this is because the boost ptr container gains access to objects as if they were objects (returning links) rather than pointers. This simplifies the use of standard functors and algorithms in containers.

The downside of smart pointers is that you are sharing property.
This is not what you really want. You want the property to be in one 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)); 
+3
source share

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

  • Define a base class (which you already do) with virtual functions that will be overridden by the Enemy and Civilian derived classes in your case.
  • You need to choose the right container that will save your object. You took std::vector<IActor> , which is not a good choice, because
    • Firstly, when you add objects to the vector, this leads to cropping of the objects. This means that instead of the entire object, only the IActor part of Enemy or Civilian saved.
    • Secondly, you need to call functions depending on the type of object ( virtual functions ), which can only happen when using pointers.

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

Here is what your code looks 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 like your source code, except for parts of smart pointers

+10
source share

My instant reaction is that you have to store smart pointers in a container and make sure that the base class defines enough (clean) virtual methods that you never need for dynamic_cast for a derived class.

+4
source share

If you want the container to have only its own elements, use the Boost pointer container: they are designed for this to work. Otherwise, use the shared_ptr<IActor> container (and, of course, use them correctly, which means that everyone who needs to exchange property use shared_ptr ).

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

void* requires that you do manual memory management for this to happen. Boost.Any is too much when types are inherited - standard polymorphism does the job.

If you need dynamic_cast or not, this is an orthogonal problem - if the container users only need the IActor interface and you either (a) perform all the functions of the interface virtual, or (b) use a non-virtual idiom interface, then you do not need dynamic_cast. If container users know that some of the IActor objects are β€œtruly” civilians and want to use things in the civil interface, but not IActor, then you will need castes (or redesign).

+3
source share

All Articles