I want to make a function which moves items from one STL list to another if they match a certain condition.
This code is not the way to do it. The iterator will most likely be invalidated by the erase() function and cause a problem:
for(std::list<MyClass>::iterator it = myList.begin(); it != myList.end(); it++)
{
if(myCondition(*it))
{
myOtherList.push_back(*it);
myList.erase(it);
}
}
So can anyone suggest a better way to do this ?
Another attempt:
Solution 1
The idea is simple. What you want to do is remove elements from one container and place them in another if a predicate is true. So take the code of the
std::remove()
algorithm, which already does the remove part, and adapt it to your extra needs. In the code above I added theelse
line to copy the element when the predicate is true.Notice that because we use the
std::remove()
code, the algorithm doesn't actually shrink the input container. It does return the updated end iterator of the input container though, so you can just use that and disregard the extra elements. Use the erase-remove idiom if you really want to shrink the input container.Solution 2
The second approach uses the STL to implement the algorithm. I personally find it more readable than the first solution, but it has two drawbacks: First, it requires the more-powerful bidirectional iterators for the input container, rather than the forward iterators we used in the first solution. Second, and this is may or may not be an issue for you, the containers are not guaranteed to have the same ordering as before the call to
std::partition()
. If you wish to maintain the ordering, replace that call with a call tostd::stable_partition()
.std::stable_partition()
might be slightly slower, but it has the same runtime complexity asstd::partition()
.Either Way: Calling the Function
Final Remarks
While writing the code I encountered a dilemma: what should the
move_if()
algorithm return? On the one hand the algorithm should return an iterator pointing to the new end position of the input container, so the caller can use the erase-remove idiom to shrink the container. But on the other hand the algorithm should return the position of the end of the result container, because otherwise it could be expensive for the caller to find it. In the first solution theresult
iterator points to this position when the algorithm ends, while in the second solution it is the iterator returned bystd::copy()
that points to this position. I could return a pair of iterators, but for the sake of making things simple I just return one of the iterators.Note that partition() gives you enough to discriminate matching objects from non matching ones. (list::splice() is cheap however)
See the following code on a concrete case inspired from Now to remove elements that match a predicate?
Erase
returns an iterator pointing to the element after the erased one:STL lists have an interesting feature: the
splice()
method lets you destructively move elements from one list to another.splice()
operates in constant time, and doesn't copy the elements or perform any free store allocations/deallocations. Note that both lists must be of the same type, and they must be separate list instances (not two references to the same list).Here's an example of how you could use
splice()
: