How to avoid memory leaks when using a vector of pointers to dynamically allocated objects in C++?

100,703

Solution 1

std::vector will manage the memory for you, like always, but this memory will be of pointers, not objects.

What this means is that your classes will be lost in memory once your vector goes out of scope. For example:

#include <vector>

struct base
{
    virtual ~base() {}
};

struct derived : base {};

typedef std::vector<base*> container;

void foo()
{
    container c;

    for (unsigned i = 0; i < 100; ++i)
        c.push_back(new derived());

} // leaks here! frees the pointers, doesn't delete them (nor should it)

int main()
{
    foo();
}

What you'd need to do is make sure you delete all the objects before the vector goes out of scope:

#include <algorithm>
#include <vector>

struct base
{
    virtual ~base() {}
};

struct derived : base {};

typedef std::vector<base*> container;

template <typename T>
void delete_pointed_to(T* const ptr)
{
    delete ptr;
}

void foo()
{
    container c;

    for (unsigned i = 0; i < 100; ++i)
        c.push_back(new derived());

    // free memory
    std::for_each(c.begin(), c.end(), delete_pointed_to<base>);
}

int main()
{
    foo();
}

This is difficult to maintain, though, because we have to remember to perform some action. More importantly, if an exception were to occur in-between the allocation of elements and the deallocation loop, the deallocation loop would never run and you're stuck with the memory leak anyway! This is called exception safety and it's a critical reason why deallocation needs to be done automatically.

Better would be if the pointers deleted themselves. Theses are called smart pointers, and the standard library provides std::unique_ptr and std::shared_ptr.

std::unique_ptr represents a unique (unshared, single-owner) pointer to some resource. This should be your default smart pointer, and overall complete replacement of any raw pointer use.

auto myresource = /*std::*/make_unique<derived>(); // won't leak, frees itself

std::make_unique is missing from the C++11 standard by oversight, but you can make one yourself. To directly create a unique_ptr (not recommended over make_unique if you can), do this:

std::unique_ptr<derived> myresource(new derived());

Unique pointers have move semantics only; they cannot be copied:

auto x = myresource; // error, cannot copy
auto y = std::move(myresource); // okay, now myresource is empty

And this is all we need to use it in a container:

#include <memory>
#include <vector>

struct base
{
    virtual ~base() {}
};

struct derived : base {};

typedef std::vector<std::unique_ptr<base>> container;

void foo()
{
    container c;

    for (unsigned i = 0; i < 100; ++i)
        c.push_back(make_unique<derived>());

} // all automatically freed here

int main()
{
    foo();
}

shared_ptr has reference-counting copy semantics; it allows multiple owners sharing the object. It tracks how many shared_ptrs exist for an object, and when the last one ceases to exist (that count goes to zero), it frees the pointer. Copying simply increases the reference count (and moving transfers ownership at a lower, almost free cost). You make them with std::make_shared (or directly as shown above, but because shared_ptr has to internally make allocations, it's generally more efficient and technically more exception-safe to use make_shared).

#include <memory>
#include <vector>

struct base
{
    virtual ~base() {}
};

struct derived : base {};

typedef std::vector<std::shared_ptr<base>> container;

void foo()
{
    container c;

    for (unsigned i = 0; i < 100; ++i)
        c.push_back(std::make_shared<derived>());

} // all automatically freed here

int main()
{
    foo();
}

Remember, you generally want to use std::unique_ptr as a default because it's more lightweight. Additionally, std::shared_ptr can be constructed out of a std::unique_ptr (but not vice versa), so it's okay to start small.

Alternatively, you could use a container created to store pointers to objects, such as a boost::ptr_container:

#include <boost/ptr_container/ptr_vector.hpp>

struct base
{
    virtual ~base() {}
};

struct derived : base {};

// hold pointers, specially
typedef boost::ptr_vector<base> container;

void foo()
{
    container c;

    for (int i = 0; i < 100; ++i)
        c.push_back(new Derived());

} // all automatically freed here

int main()
{
    foo();
}

While boost::ptr_vector<T> had obvious use in C++03, I can't speak of the relevance now because we can use std::vector<std::unique_ptr<T>> with probably little to no comparable overhead, but this claim should be tested.

Regardless, never explicitly free things in your code. Wrap things up to make sure resource management is dealt with automatically. You should have no raw owning pointers in your code.

As a default in a game, I would probably go with std::vector<std::shared_ptr<T>>. We expect sharing anyway, it's fast enough until profiling says otherwise, it's safe, and it's easy to use.

Solution 2

The trouble with using vector<T*> is that, whenever the vector goes out of scope unexpectedly (like when an exception is thrown), the vector cleans up after yourself, but this will only free the memory it manages for holding the pointer, not the memory you allocated for what the pointers are referring to. So GMan's delete_pointed_to function is of limited value, as it only works when nothing goes wrong.

What you need to do is to use a smart pointer:

vector< std::tr1::shared_ptr<Enemy> > Enemies;

(If your std lib comes without TR1, use boost::shared_ptr instead.) Except for very rare corner cases (circular references) this simply removes the trouble of object lifetime.

Edit: Note that GMan, in his detailed answer, mentions this, too.

Solution 3

I am assuming following:

  1. You are having a vector like vector< base* >
  2. You are pushing the pointers to this vector after allocating the objects on heap
  3. You want to do a push_back of derived* pointer into this vector.

Following things come to my mind:

  1. Vector will not release the memory of the object pointed to by the pointer. You have to delete it itself.
  2. Nothing specific to vector, but the base class destructor should be virtual.
  3. vector< base* > and vector< derived* > are two totally different types.
Share:
100,703
akif
Author by

akif

Updated on July 05, 2022

Comments

  • akif
    akif almost 2 years

    I'm using a vector of pointers to objects. These objects are derived from a base class, and are being dynamically allocated and stored.

    For example, I have something like:

    vector<Enemy*> Enemies;
    

    and I'll be deriving from the Enemy class and then dynamically allocating memory for the derived class, like this:

    enemies.push_back(new Monster());
    

    What are things I need to be aware of to avoid memory leaks and other problems?

  • akif
    akif over 14 years
    Your assumptions are absolutely correct. Sorry, I wasn't able to explain properly. Is there anything else?
  • Kiran Kumar
    Kiran Kumar over 14 years
    If possible avoid raw pointers, and use the methods described in GMan's answer.
  • Steve Holgado
    Steve Holgado over 14 years
    If he's actually writing gamecode (as the example kind of alludes to) then a ref counted pointer (or however boost implemented the shared pointer) is likely overly expensive.. a constant memory footprint (especially for AI objects) is a loftier design goal than removing a for loop to deallocate.
  • GManNickG
    GManNickG over 14 years
    I think your point is very good, actually. Should I edit it in? I'm always unsure at this point. If I edit my answer so it's more complete, I feel like I'm "stealing" rep from other people.
  • akif
    akif over 14 years
    Which one should I choose b/w Pointer Contains and Shared Pointers and why?
  • sbi
    sbi over 14 years
    @GMan: Go ahead and improve the answer that's on top of the stack. Your answer is good and detailed and definitley deserves to be there. To hell with the rep, if there's one less programmer out there doing this kind of things, that will help us all a lot more than any rep points. :)
  • akif
    akif over 14 years
    and perhaps will help others in future, thus saving others time :)
  • sbi
    sbi over 14 years
    @Dan: Some way or another you will have to do the cleanup and if that's too slow, the question isn't which way to do it, but how to avoid having to do it in the first place. If you can't get around it, use the cleanest way first, then measure, and only try to improve afterwards. Boost means several thousand pairs of keen eyes improving the code. Hard to beat that: I have seen boost's shared_ptr outperforming a custom smart pointer using a special-purpose allocator in CPU/GPU-intensive 3D applications. Until you measure, you never know...
  • GManNickG
    GManNickG over 14 years
    Updated my answer. Luckily our 'answers' matched this time, sbi. :P (Profile!)
  • sbi
    sbi over 14 years
    GMan, I wasn't aware that our answers matching is something to note. :^> I usually find myself agreeing with you and you're among those who's answers I value (and upvote regularly). Have we disagreed a lot recently?
  • GManNickG
    GManNickG over 14 years
    Ha, no I was referring to my comments on your answer below on my unsureness to edit in your information. My comment isn't an issue if your information is my answers. Lame connection. :P Anyway, thank you for the compliment.
  • sbi
    sbi over 14 years
    @GMan: Now you have me totally confused. :o> I guess I'll just stick to "isn't an issue" then, OK?
  • akif
    akif over 14 years
    Thanks sir, really helpful and detailed answer
  • Steve Holgado
    Steve Holgado over 14 years
    @sbi I'm not advocating a different shared_ptr, I'm advocating a different approach to memory management. Shared pointers are very likely inappropriate in the game code case. In fact, they're totally inappropriate for the example the original poster submitted. Most of my argument is summarized here: bureau14.fr/blogea/2009/08/smart-pointers-are-overused
  • sbi
    sbi over 14 years
    @Dan: The article you linked to had (besides sound advice to not to mindlessly copy around smart pointers) a nice example of a dynamically allocated object that lives for the applications lifetime. I suppose that's quite different from the most likely constantly changing number of enemies in a computer game. These things will most likely have to be allocated dynamically and a way has to be found to manage their lifetime. Smart pointers are (IMO) the default off-the-shelf solution to this and I wouldn't use anything else unless profiling forces me to.
  • Mathias
    Mathias over 14 years
    My word! Friendly and cooperative discourse, let alone agreement in an online discussion? Totally unheard of! Nice work :)
  • beldaz
    beldaz over 11 years
    This answer is referenced by others, but it's a bit dated now. It might be worth revising to mention std::unique_ptr instead of std::auto_ptr or boost::shared_ptr.
  • beldaz
    beldaz over 11 years
    This doesn't seem to answer the question at all. If all you are concerned about is the possibility of multiple pointers to the same object you should just use a reference-counted smart pointer, such as the boost::smart_ptr.
  • somerandomdev49
    somerandomdev49 about 4 years
    i'm getting a segfault when deleting an element of the vector, although I allocate it with new. (I can't use smart pointers)
  • GManNickG
    GManNickG about 4 years
    @CoolMikeWhoDoesSomething: I'd recommend asking a new question for your issue. Note that if you don't have any smart pointers readily available, it is highly worth the time to write your own unique_ptr and go from there.
  • somerandomdev49
    somerandomdev49 about 4 years
    @GManNickG I don't want to make a question, because my code is too complicated to put in a stackoverflow question and I don't think that the problem will continue to exist if I make the code simple. Although thanks for your advice, because my own smart pointers will be very nice for the project I'm working on :)