Click here to Skip to main content
14,926,224 members
Articles / Programming Languages / C++
Article
Posted 27 Jan 2018

Tagged as

Stats

16K views
78 downloads
8 bookmarked

Erase-remove Idiom Revisited

Rate me:
Please Sign up or sign in to vote.
5.00/5 (7 votes)
27 Jan 2018CPOL2 min read
Always use Erase-remove Idiom to erase vector elements

Recently, I reread Erase-remove Idiom on classic Effective STL by Scott Meyers, which is dated by now, so I also consulted the same topic on another STL book published in 2017. How to remove elements from container is a common C++ interview question, so you may want to sit up and pay attention here.

std::vector contains an erase function to remove elements. The problem is once erase is called, all iterators are invalidated. That means if std::vector contains more than 1 element which satisfy the criteria to be removed, developer has to restart iterating by getting a new iterator from vector::begin(). A much better way is to use erase-remove Idiom. First, we use the STL remove/remove_if to move the removed elements to back of the vector container. STL remove/remove_if, despite their name, cannot remove anything from the std::vector because they operate on iterators and are not aware of the underlying container object. So after calling remove/remove_if, we have to call container's erase to actually erase the elements.

remove takes in value to be removed as the last argument: Any element matching the same value is 'removed'. Whereas remove_if takes in functor or lambda as predicate in the last argument: Any element which satisfies predicate is 'removed'.

C++
// initializes a vector that holds the numbers from 0-9.
std::vector<int> v = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
print(v);

// removes all elements with the value 5
v.erase( std::remove( v.begin(), v.end(), 5 ), v.end() );
print(v);

// removes all odd numbers
v.erase( std::remove_if(v.begin(), v.end(), is_odd), v.end() );
print(v);
C++
std::remove/std::remove_if Output:
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 6 7 8 9
0 2 4 6 8

From Wikipedia Erase–remove idiom

STL remove/remove_if returns Past-the-end iterator for the new range of values (if this is not end, then it points to an unspecified value, and so do iterators to any values between this iterator and end).

If no element is 'removed', remove/remove_if returns v.end(), so the erase call actually does nothing and no element is erased.

C++
v.erase( v.end(), v.end() );

STL remove/remove_if shifts the rest of the elements to fill the gap left by removed element. The performance could be improved if maintaining relative order between the elements is not a requirement. Mentioned in Mastering the C++17 STL by Arthur O'Dwyer', unstable_remove has been proposed for future standardization but has not yet adopted into the STL. unstable_remove does not shift the remaining element to fill the 'hole', instead it moves the last element to fill the 'hole'.

C++
namespace my 
{
    template<class BidirIt, class T>
    BidirIt unstable_remove(BidirIt first, BidirIt last, const T& value)
    {
        while (true) 
        {
            // Find the first instance of "value"...
            first = std::find(first, last, value);
            // ...and the last instance of "not value"...
            do 
            {
                if (first == last) 
                    return last;
                --last;
            } 
            while (*last == value);
            // ...and move the latter over top of the former.
            *first = std::move(*last);
            // Rinse and repeat.
            ++first;
        }
    }
    template<class BidirIt, class Pred>
    BidirIt unstable_remove_if(BidirIt first, BidirIt last, Pred predicate)
    {
        while (true) 
        {
            // Find the first instance of "value"...
            first = std::find_if(first, last, predicate);
            // ...and the last instance of "not value"...
            do 
            {
                if (first == last) 
                    return last;
                --last;
            } 
            while (predicate(*last));
            // ...and move the latter over top of the former.
            *first = std::move(*last);
            // Rinse and repeat.
            ++first;
        }
    }
} // namespace my

unstable_remove usage is the same as std::remove but their output is different.

C++
// initializes a vector that holds the numbers from 0-9.
std::vector<int> v = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
print(v);

// removes all elements with the value 5
v.erase( my::unstable_remove( v.begin(), v.end(), 5 ), v.end() );
print(v);

// removes all odd numbers
v.erase( my::unstable_remove_if(v.begin(), v.end(), is_odd), v.end() );
print(v);
C++
my::unstable_remove/my::unstable_remove_if Output:
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 9 6 7 8
0 8 2 6 4

In his book, Arthur also noted:

For std::list container, std::list::erase can be much faster than the Erase-remove idiom on a std::list.

Special points of interest: This idiom can be used with std::unique where consecutive same values are moved to the back of the container. The erase–remove idiom cannot be used for containers that return const_iterator (for example, set). Another important note is erase–remove idiom can only be used with containers holding elements with full value semantics without incurring resource leaks, meaning raw pointer pointing to allocated memory should not be stored in the container, smart pointer is fine.

Reference

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Shao Voon Wong
Software Developer (Senior)
Singapore Singapore
Shao Voon is from Singapore. CodeProject awarded him a MVP in recognition of his article contributions in 2019. In his spare time, he prefers to writing applications based on 3rd party libraries than rolling out his own. His interest lies primarily in computer graphics, software optimization, concurrency, security and Agile methodologies.

You can reach him by sending a message on CodeProject or at his Coding Tidbit Blog!

Comments and Discussions

 
Suggestionlooks like we're missing a function erase_if() Pin
Stefan_Lang31-Jan-18 21:02
mveStefan_Lang31-Jan-18 21:02 
GeneralRe: looks like we're missing a function erase_if() Pin
Shao Voon Wong6-Feb-18 2:37
mvaShao Voon Wong6-Feb-18 2:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.