0

I'm new to C++ and have a question regarding memory management.

In the header, I have this:

std::vector<Obstacle::Obstacle*> obstacles;

and in the .cpp I do this:

Circle *circle = new Circle(x, y, radius);
obstacles.push_back(circle);

where Circle is a subclass of Obstacle.

My question is when should I call delete on the elements which are in the vector? I have heard each new should be balanced by a delete. Do I need to in the destructor loop through the vector and call delete on each element? Isn't there a more elegant way?

Thanks

Isak T.
  • 335
  • 1
  • 3
  • 16

3 Answers3

10

You have to call delete on the elements before you clear the vector, or before the vector goes out of scope iff the vector owns the objects pointed at. A more elegant solution is to have the vector hold smart pointers. The particular type of smart pointer should depend on the ownership policy. For example, a vector owning the pointed-at objects should use C++11 std::unique_ptr:

std::vector<std::unique_ptr<Obstacle>> obstacles;

Of course, all of the above is under the assumption that you actually have strong reasons to use pointers. Often the best solution is the simplest ones: hold items by value:

std::vector<SomeType> things;

Note that this doesn't apply in your case, where you are storing pointers to objects derived from a base class, since storing values of base type would result in object slicing.

Edit: One simple way to ensure the elements are deleted when the vector goes out of scope is to write a scope guard class:

template <typename CONTAINER>
struct PtrContainerGuard
{
  PtrContainerGuard(CONTAINER& container) : c_(container) {}
  ~PtrContainerGuard()
  {
    for (typename CONTAINER::iterator it = c_.begin(); it != c_.end(); ++it)
      delete (*it);
  }
private:
  CONTAINER& c_;

}

then

std::vector<Obstacle*> obstacles;
PtrContainerGuard<std::vector::Obstacle*> p(obstacles);
juanchopanza
  • 223,364
  • 34
  • 402
  • 480
  • +1: This answer is a true question: who owns what, and when? Only the domain knowledge can give you a way to decide. It's the main motivation of Garbage Collectors. Add to this problem a little bit of concurrency and headache is for you! ;-) – Aubin Oct 14 '12 at 09:23
  • Not the down voter, but it should be made clear that the last suggestion (holding items by value) won't work for polymorphic data. The `std::vector` cannot hold `Circle` objects. – Michael Burr Oct 14 '12 at 09:24
  • @MichaelBurr I edited that a few minutes ago. I hope it is clear enough. – juanchopanza Oct 14 '12 at 09:25
  • So if I don't want to use smart pointers, I should loop through the elements and delete them in the destructor? I don't think I can do without pointers as Obstacle is an abstract class (?). – Isak T. Oct 14 '12 at 09:27
  • @MichaelFrost assuming the vector owns the objects pointed at, then yes, you will have to iterate and delete. – juanchopanza Oct 14 '12 at 09:29
  • For example Obstacles are owned by Scene and shared by Renderer. You can decide only by drawing all your software architecture with UML. And using dynamic diagrams, not only statics ones. – Aubin Oct 14 '12 at 09:31
  • an [addition](http://stackoverflow.com/a/3283795/847349) to the solution with std::unique_ptr – Dmitry Ledentsov Oct 14 '12 at 09:32
  • @MichaelFrost another option is to write a small wrapper scope guard class that makes sure to delete the elements. I added an example. – juanchopanza Oct 14 '12 at 09:46
  • The usage of the PtrContainerGuard is the same as composition, it's very dangerous because pointers may be shared and used after delete. It's not a syntax problem but a semantic one. The live cycle of an application object should be analyzed as a high level, not at low level – Aubin Oct 14 '12 at 10:23
  • @Aubin here, the guard is only a safe replacement for iterating and deleting before leaving scope. Of course, the same ownership issues apply. – juanchopanza Oct 14 '12 at 10:26
  • @juanchopanza: Yes, you're right but be careful when you give a shotgun to a guy. Once a day he will use it... ;-) Analysis should be done before programming with the Occam's razor in mind. Be simple, be simple. – Aubin Oct 14 '12 at 10:31
0

Why not use shared_ptr? You don't have to create new objects and worry about deleting them if you use them.

typedef shared_ptr<Obstacle> ObstaclePtr;
int main()
{
std::vector<ObstaclePtr> obstacles;
//Create objets using shared_ptr and push them in vector
ObstaclePtr obstacle1(new Circle());
obstacles.push_back(obstacle1);

ObstaclePtr obstacle2(new Circle());
obstacles.push_back(obstacle2);
//When vector obstacles goes out of scope here, all circles inside are destructed!
 }
CPJoshi
  • 387
  • 4
  • 11
-1

Yes, there is a more elegant way. Throw away all your pointers.

std::vector<Obstacle::Obstacle> obstacles;

Circle circle(x, y, radius);
obstacls.push_back(circle);

Nothing was new'ed, nothing needs to be deleted, you save a memory allocation, and access to the objects stored in the vector becomes more efficient.

Also, your code will no longer make the eyes bleed of more experienced C++ developers.

All in all, I call that a win. :)

jalf
  • 243,077
  • 51
  • 345
  • 550
  • 2
    Isn't this suggestion vulnerable to slicing? – Magnus Hoff Oct 14 '12 at 09:19
  • 1
    And you've pushed only the Obstacle part into the vector. All the circle-specific information is lost... – Armen Tsirunyan Oct 14 '12 at 09:19
  • This answer not the question. Composition (in opposition to aggregation) don't solve all problems. Once two or more objects share the same instance of another object, pointers go back (even 'smart') – Aubin Oct 14 '12 at 09:26
  • @BenjaminLindley: yes, it is. I could say I'd just woken up when I wrote it, but ultimately, yes, you're right, it's a brainfart and it causes slicing and undefined behavior. In this case, smart pointers would be a better solution. The point I intended to get across, however, stands: don't use (raw) pointers (well, don't use them like *this*, at any rate). Don't put yourself in a situation where you have to explicitly call `delete` *ever*. Only call `new` if you then *immediately* store the resulting pointer into a smart pointer of some sort. I will fix up my answer when I have a moment. :) – jalf Oct 14 '12 at 11:04