Search code examples
c++for-loopiteratorunique-ptrstdset

Iterating over std::set<unique_ptr>, how to keep track which ones to remove?


I need to loop over some objects of class T.

They are stored in an std::set<std::unique_ptr<T>> tees.

The main purpose of the loop's body is to use the objects, but by doing that I will also find out when some of the objects are no longer needed and can be deleted.

I am using a range-based for loop for iterating over the unique_ptrs:

for (std::unique_ptr<T> & tee : tees)

I know I cannot call tees.erase(tee) inside the loop (UB). Therefore I should collect the unique_ptrs that need to be deleted in a helper collection. Problem: The pointers are unique, therefore I cannot copy them into the helper collection.

I could collect the raw pointers in a std::set<T*>, but how would I use these after the loop to delete the matching unique_ptrs from the tees collection? Also, collecting the raw pointers again somehow feels wrong when I made the effort to use smart pointers in this problem.

I could switch to shared_ptr, but the pointers would only ever be shared for the purpose of deleting the objects. Does not feel right.

I could switch from range-based for to something else, like handling the iterators myself, and get the next iterator before deleting the entry. But going back to pre-C++11 techniques also does not feel right.

I could switch to std::remove_if. (EDIT: I can't, actually. Explained in comments below this question and below the accepted answer.) The loop's body would move into the unary_predicate lambda. But the main purpose of the loop is not to determine whether the objects should be deleted, but to make use of them, altering them.

The way of least resistance seems to be to go back to iterator-handling, that way I do not even need a helper collection. But I wonder if you can help me with a C++11-ish (or 14,17) solution?


Solution

  • I don't think you are going to find anything easier than

    for(auto it = container.begin(), it != container.end();)
    {
        //use *it here
        if(needs_to_be_erased)
            it = container.erase(it);
        else
            ++it;
    }
    

    since std::set does not provide mutable access to its elements any sort of transform or remove will not work. You would have to build a container of iterators and then after you process the set go through that container of iterators calling erase for each one.