tags:

views:

702

answers:

4

I have a collection of polymorphic objects, all derived from my Animal class: Cat, Dog, and MonkeyFish.

My usual mode of operation is to store these objects in a vector of Animal pointers, like so:

std::vector< Animal * > my_vector;

my_vector.push_back( new Animal_Cat() );
my_vector.push_back( new Animal_Dog() );
my_vector.push_back( new Animal_MonkeyFish() );

And life is great...or is it?

I've recently been told that I should really try to avoid allocating memory in this fashion, because it makes memory management a chore. When I need to destroy my_vector, I have to iterate through all the elements and delete everything.

I don't think that I can store a vector of references (I might be wrong about this), so it seems like storing a vector of Animal objects is my only alternative.

When should I choose to use a vector of pointers versus a vector of objects? In general, which method is preferable? (I would like to reduce object copying as much as possible.)

+8  A: 

In this case, storing a vector of Animal would not work for you, as your animals have different sizes, and you wouldn't be able to store the derived objects in the spaces intended to hold the base class. (And even if they're the same size, you won't get the intended polymorphic effect, as base class methods will be executed - the virtualness of a method doesn't come into play unless you access it through a pointer or reference.)

If you want to avoid the annoyance of managing the memory yourself, you could consider storing a smart pointer such as a shared_ptr (note that auto_ptr doesn't work with STL containers, according to Max Lybbert), or some variant thereof. That way you can still use your polymorphic class, but it's a little less work for you.

There's no real hard and fast rules about when to use objects and pointers, although it's worth noting that in some cases, like yours, objects just aren't going to work for you. I tend to use objects whenever nothing precludes it though, although you do have to be concerned about expensive copy operations as you note (although sometimes they can be ameliorated by passing containers by reference).

Blair Conrad
Huh. Did not know that. How... unfortunate.
Blair Conrad
I have never tried it so I am wondering, why does auto_ptr not work with stl containers?
James Matta
Ah, yes. Makes sense.
Blair Conrad
Darn, oh well I guess I shall remove my answer then, thank you for the information. Though to be honest it probably would not be that hard to write a primitive version of auto_ptr that could get the job done and even know to use delete [] or delete as needed.
James Matta
@James: You'd think there'd be an easy way, but in fact reference counting was the only option until the new stuff in c++0x arrives.
Zan Lynx
Meh I just handle my memory by hand and I have never used the boost libraries before. Until I got to grad school something like boost was far more than I needed and now that I am here I haven't had a programming task hard enough that I needed boost for my data analysis code.
James Matta
@Zan Lynx, I probably would have just complexified it by having an attribute that users would have to set to tell it whether to use delete [] or delete. less intuitive but it gets the job done.
James Matta
@James: it won't make them work inside STL containers. Only reference counting will.
vava
An auto_ptr<> asserts ownership, and there can be only one. Copying an auto_ptr<> destroys the original. This doesn't play at all with STL containers, which need to do some copying and swapping around.
David Thornley
+13  A: 

You should use a vector of objects whenever possible; but in your case it isn't possible.

Containers of pointers let you avoid the slicing problem. But then you have to call delete on each element, like you are doing. That's annoying but possible. Unfortunately there are cases (when an exception is thrown) where you can't be sure that delete is properly called, and you end up with a memory leak.

The main solution is to use a smart pointer. The STL currently comes with auto_ptr, but that cannot be used in a standard container. Until C++0x -- which comes with new smart pointers -- the best solution is Boost smart pointers. You can use your compiler's implementation of those smart pointers a la TR1, but unfortunately there is some disagreement on the namespace for TR1 functions (Visual C++ puts them in std::, while GCC puts them in std::tr1::).

Max Lybbert
The Boost smart pointers made it into a technical report, so you may have std::tr1::shared_ptr<>. VC++ 9 in VS 2008 does.
David Thornley
for reference the c++0x unique_pte, which is pretty much a replacement for auto_ptr, will work in containers due to rvalue references and move semantics
jk
+8  A: 

Rather than using shared_ptr with standard STL containers, take a look at the Boost Pointer Container Library. It is designed to solve exactly this problem.

Simon C.
A: 

If you ever hear the argument but it'll be so costly to copy them structures all the time when you want to use full objects instead of pointers in a vector, then your 2 main arguments are:

  1. We don't need to worry about the lifetime issues of the pointers, which means no leaks from that particular code (unless, of course, the structures themselves have pointer data, but that's another story).
  2. The data locality of adjacent structures in memory will boost performance in typical usage scenarios, not slow things down like pointer indirection would (relatively speaking).

The added cost of copying is normally taken when adding stuff to the container, not when using the data - think a bit about it: what do you do most? add items or use them?

However, when adding polymorphical objects, the pointers are necessary to avoid slicing.

Johann Gerell