views:

178

answers:

6

This is an attempt to rewrite some old homework using STL algorithms instead of hand-written loops and whatnot.

I have a class called Database which holds a Vector<Media *>, where Media * can be (among other things) a CD, or a Book. Database is the only class that handles dynamic memory, and when the program starts it reads a file formatted as seen below (somewhat simplified), allocating space as it reads the entries, adding them to the vector (v_) above.

CD
Artist
Album
Idnumber
Book
Author
Title
Idnumber
Book
...
...

Deleting these object works as expected when using a hand-written loop: EDIT: Sorry I spoke too soon, it's not actually a 'hand-written' loop per-se.. I've been editing the project to remove hand-written loops and this actually uses the find_if algorithm and a manual deletion, but the question is till valid. /EDIT.

typedef vector<Media *>::iterator v_iter;

...
void Database::removeById(int id) {
    v_iter it = find_if(v_.begin(), v_.end(), Comparer(id));
    if (it != v_.end()) {
        delete *it;
        v_.erase(it);
    }
}

That should be pretty self-explanatory - the functor returns true if it find an id matching the parameter, and the object is destroyed. This works and valgrind reports no memory leaks, but since I'd like to use the STL, the obvious solution should be to use the erase-remove idiom, resulting in something like below

void Database::removeById(int id) {
    v_.erase(remove_if(v_.begin(), v_.end(), Comparer(id)), v_.end());
};

This, however, 'works' but causes a memory leak according to valgrind, so what gives? The first version works fine with no leaks at all - while this one always show 3 allocs 'not freed' for every Media object I delete.

+3  A: 

In the first version, you're carefully calling delete *it. In the updated version, you're not.... v_.erase is de-allocating the pointer, but not the object the pointer references.

jwismar
Oh, of course! But is there any way to make the second version work as expected using some STL algorithm without explicitly calling delete? Or am I stuck at the first version here?
citizencane
A: 

This means that the second version is incorrect. If you want to use it, consider shared_ptr:

typedef vector< shared_ptr<Media> > MediaVector;
...
Alex Farber
+2  A: 

You already have a specific answer. However, your underlying problem is that you're using naked pointers to manually manage resources. That is hard, and sometimes it hurts.
Change your type to std::vector<std::shared_ptr<Media> > and things become much easier.

(If your compiler doesn't yet support std::shared_ptr, it will very likely have std::tr1::shared_ptr. Otherwise use boost's boost::shared_ptr.)

sbi
I would not recommend `shared_ptr` per se, but rather `unique_ptr`.
Matthieu M.
@Matthieu: Are there actually STL implementations out there which do not require container element types to be copy-constructible and assignable?
sbi
Good question :) In fine I guess it will be the case, since the standard mandates the support of move semantics but I have no idea about the actual state of implementation. I guess it's another case for the Pointer Container library then: I don't like using `shared_ptr` for this because of the risk of leaking ownership...
Matthieu M.
@Matthieu: Weak pointers were invented so you wouldn't risk leaking ownership.
sbi
But dereferencing an iterator yields a copy of a `shared_ptr`, not a `weak_ptr`, and thus your container is effectively leak ownership. So of course you'll probably be careful and tuck the container neatly in its own class... but it's still easier to use the Pointer Container library I think.
Matthieu M.
@Matthieu: I see. However, what about using `unique_ptr` in containers, then? If I accidentally make a copy, wouldn't that remove the object from the container? To me that seems worse than `shared_ptr`.
sbi
+1  A: 

There is no call to delete in the second version of removeById. Erasing an element from a vector of pointers doesn't call delete on the pointer.

The erase-remove version presented is roughly equivalent to using the original removeById, but with a small change:

void Database::removeById(int id) {
    v_iter it = find_if(v_.begin(), v_.end(), Comparer(id));
    if (it != v_.end()) {
        //delete *it;
        v_.erase(it);
    }
}

Hopefully this makes it clearer what's going on, and why the leaks are appearing.

brone
Yes, I thought that erase called delete on whatever object the remove_if iterator returned. Lesson learned. Thank you!
citizencane
erase-remove version is NOT equivalent to the find_if version, see my comment to the question.
smerlin
You're quite right, though I put a "roughly" in there so that I could argue I'm correct, even when I'm not ;)
brone
+1  A: 

This is why you should always, ALWAYS use smart pointers. The reason that you have a problem is because you used a dumb pointer, removed it from the vector, but that didn't trigger freeing the memory it pointed to. Instead, you should use a smart pointer that will always free the pointed-to memory, where removal from the vector is equal to freeing the pointed-to memory.

DeadMG
Thank you! All answers have been very good and I'll accept this since it offers a solution.
citizencane
Smart pointers are not *always* a good fit for STL containers, or your project in particular. YMMV
Blair Holloway
@Blair: That's not true. What IS true is that STL smart pointers are not always a good fit for STL containers. You can always roll your own. In addition, in C++0x, some of those things are fixed, like the new std::unique_ptr.
DeadMG
+1  A: 

Obviously your vector OWNS the objects it contains.

As you noticed the STL containers are not OO-friendly in the sense that they do not easily adapt to inheritance. You need to use dynamically allocated memory and all its woes for that.

The first and easy solution is to replace plain pointers (please, no more) by smart pointers. People will usually recommend shared_ptr, but if you have access to C++0x prefer them unique_ptr.

There is also another solution that you should considerate. Containers that mimicks the STL ones but have been designed to work with inheritance hierarchy. They use pointers under the hood (obviously) but relieves you of the tedium of managing the memory yourself and the overhead associated with smart pointers (especially shared_ptr).

Behold the Boost Pointer Container library!

It is clearly here the best solution, for it has been created exactly for the problem you are trying to solve.

Furthermore, its containers are (mostly I think) STL compliant, so you'll be able to use the erase/remove idiom, the find_if algorithm, etc...

Matthieu M.