views:

531

answers:

4

Suppose I have a STL map where the values are pointers, and I want to delete them all. How would I represent the following code, but making use of std::for_each? I'm happy for solutions to use Boost.

for( stdext::hash_map<int, Foo *>::iterator ir = myMap.begin();
     ir != myMap.end();
     ++ir )
{
  delete ir->second; // delete all the (Foo *) values.
}

(I've found Boost's checked_delete, but I'm not sure how to apply that to the pair<int, Foo *> that the iterator represents).

(Also, for the purposes of this question, ignore the fact that storing raw pointers that need deleting in an STL container isn't very sensible).

Note: I have subsequently found and listed a one-line answer below... but the code is pretty awful so I've accepted GMan's saner answer.

+8  A: 

You have to make a function object:

struct second_deleter
{
    template <typename T>
    void operator()(const T& pX) const
    {
        delete pX.second;
    }
};

std::for_each(myMap.begin(), myMap.end(), second_deleter());

If you're using boost, you could also use the lambda library:

namespace bl = boost::lambda;
std::for_each(myMap.begin(), myMap.end(), second_deleter(),
                bl::bind(bl::delete_ptr(), 
                bl::bind(std::select2nd<myMap::value_type>(), _1));

But you might try the pointer containers library which does this automatically.

Note you are not using a map, but a hash_map. I recommend you switch to boost's unordered_map, which is more current. However, there doesn't seem to be a ptr_unordered_map.

For safety, you should wrap this thing up. For example:

template <typename T, typename Deleter>
struct wrapped_container
{
    typedef T container_type;
    typedef Deleter deleter_type;

    wrapped_container(const T& pContainer) :
    container(pContainer)
    {}

    ~wrapped_container(void)
    {
        std::for_each(container.begin(), container.end(), deleter_type());
    }

    T container;
};

And use it like:

typedef wrapped_container<
            boost::unordered_map<int, Foo*>, second_deleter> my_container;

my_container.container./* ... */

This ensures no matter what, your container will be iterated through with a deleter. (For exceptions, for example.)

Compare:

std::vector<int*> v;
v.push_back(new int);

throw "leaks!"; // nothing in vector is deleted

wrapped_container<std::vector<int*> > v;
v.container.push_back(new int);

throw "no leaks!"; // wrapped_container destructs, deletes elements
GMan
I figured it might be something like that. I had hoped it would be possible to do this without defining my own type. Is it possible to use boost::bind or select2nd or somesuch, I wonder?
stusmith
doesn't this technically invoke UB if the map decides to re-balance itself and thus starts copying deleted pointers around? maybe null them as well? - just noticed its a hash_map not a map so it wont be re-balancing
jk
@jk: this isn't removing anything from the map, just deleting objects that it points to.
Mike Seymour
@jk: Why would a map randomly set pointers in the map to null? That would be a terribly broken map.
GMan
it wouldnt maybe you should null them after deleting them
jk
@jk: There's no purpose to nulling after deleting. This isn't done until the map is going out of scope. Nulling pointers after deleting them is my number one hated habit. :]
GMan
the OP didnt say this was just before the container itself is destroyed. if anything else happens that cause the container to copy any of its internal stuff, then it will copy a deleted pointer, which is technically UB iirc. nulling the pointers will make them safe to copy again.
jk
@jk: Fair enough. It's pretty inane to maintain a container after deleting everything in it though. A `clear()` should be in store after deleting them all. *If you're keeping a pointer around that you've deleted, you're doing it wrong.* That's why there's never a reason to set a pointer to null after deleting it; once you've deleted it, **don't keep it around**.
GMan
A: 

If it's possible, why don't you use smart pointers in your map?

Jacob
It should be possible at some point. I'm refactoring some rather old code at the moment. In truth, I was just curious as to whether I could do this in one line of code. But you're absolutely right, eventually I'd like to get there... but this is part of a million lines of 15-year-old code.
stusmith
I see your point, but the use of smart pointers here removes the need to refactor and debug member deletion. One less memory management to worry about going forward. Any time I use new/delete I think really hard about whether that's needed. A personal "code smell" (per Martin Fowler), if you like.
Steve Townsend
Of course, if your old code returns a map, then the `for_each` approach is probably your best bet - but if you had some hand in creating the map, I'd recommend using smart pointers.
Jacob
If you need to dynamically allocate elements, yet the concept of shared ownership doesn't apply, you can take a look at `boost::ptr_map`.
Emile Cormier
+2  A: 

Have you tried using BOOST_FOREACH ? That should allow you to do that in a line without creating your own functor.

I have not tested the following code but it should look something like this(if not exactly):

typedef stdext::hash_map<int, Foo *> MyMapType; //see comment.
BOOST_FOREACH( MyMapType::value_type& p, myMap )
{
    delete p.second;
}

Well thats more than 1 line, due to the typedef :)

Akanksh
Generally sound. Unfortunately, it is a macro and cannot handle commas between `<>`
UncleBens
ah yes, a typedef would be needed, sorry about that, no longer one line.
Akanksh
A: 

OK, I found out how to do it in one line... but I don't think I would ever actually do the following in real code!

std::for_each( mayMap.begin()
             , myMap.end()
             , boost::bind( &boost::checked_delete<Foo>
                          , boost::bind( &stdext::hash_map<int, Foo *>::value_type::second, _1 ) ) );

However I'm going to accept GMan's answer because I like his idea of a wrapped container, and my answer, despite being one line as requested, is just plain nasty.

stusmith