21
Dec
2021

Move Semantics in the std::remove and std::remove_if

std::remove and std::remove_if functions are Inside the C++ algorithm library. We shouldn’t forget these functions names contain “move”

These functions are valid only for more assignable objects.

Remove functions returns the end of the iterator for the new range. These functions only remove the elements by shifting. It means, the remove() functions don’t erase the elements that are removed and do not shrink the size of the container or string. The moved-from elements are in an unspecified state. We can erase them using the new-range iterators which are returned by remove functions.

After a basic usage of the std::remove_if example, we will analyze in-depth regarding move semantics.

Suppose we have a string and we want to remove all white space and punctuation inside the string, the function remove_if can be useful. Here is an example of it:

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

struct punctEnemy
{
  static const char whiteSpace = ' ';
  
  bool operator()(const char &c)
  {
    return (c == whiteSpace || ispunct(c)) ? true : false;
  };
};

int main()
{

  string preLude{ "Hello! Embedded World!, Welcome!." };

  auto newEnd = std::remove_if(preLude.begin(), preLude.end(), punctEnemy());

  cout << endl << "\nAfter removing string with newEnd of newRange:\n";
  for_each(preLude.begin(), newEnd, 
  [](const char &c) {cout << c;	});

  cout << endl << "\nAfter removing - full string:";
  cout << endl << preLude;

  preLude.erase(newEnd, preLude.end());
  cout << endl << "\nAfter erasing - full string:";
  cout << endl << preLude;

  return 0;
}

The output of code:
After removing with newEnd of newRange:
HelloEmbeddedWorldWelcome

After removing – full string:
HelloEmbeddedWorldWelcomeelcome!.

After erasing – full string:
HelloEmbeddedWorldWelcome

 

In the example’s output above, we see the string that was removed whitespaces and punctuation:

After removing – full string:

HelloEmbeddedWorldWelcomeelcome!.

As is seen there are additional characters in the string: “elcome!.” Every whitespace and punctation character was replaced by our expected characters. After that movement, we have additional characters. Because remove() functions don’t erase the elements. With the erase function that is a member of std::string, deleted the moved-from or untouched characters that we want to leave.

If we want to see these moved-from state elements directly, we can declare a user-defined move assignment function. Suppose we have a class named myString that has a string as a member that is initialized by the constructor. We want to observe moved-from state after removing operation for any container is initialized with class myString. myString class has user-defined move semantics functions that move our string and the other members.

Here is an example to observe our strings moved from the state.

#include <iostream>
#include <algorithm>
#include <list>
#include <vector>
using namespace std;

class myString {

private:
    string str;
    bool movedFrom{
        false
    };

public:
    myString(const string& s)
        : str{s}
    {
    }

    myString(const char* s) //for string literal
        : myString{std::string(s)}
    {
    }

    myString(myString&& o) noexcept
    {
        str = std::move(o.str);
        movedFrom = o.movedFrom;
        o.movedFrom = true;
    }

    myString& operator=(myString&& o) noexcept
    {
        str = std::move(o.str);
        movedFrom = o.movedFrom;
        o.movedFrom = true;
        return *this;
    }
    myString& operator=(const myString&) = default; //enable copy
    myString(const myString&) = default; //enable copy

    string getString() const
    {
        assert(!movedFrom);
        return str;
    }

    friend ostream& operator<<(ostream& ostr, const myString& p)
    {
        return ostr << (p.movedFrom ? "Moved From" : p.str);
    }
};

struct viewer {

    void operator()(const myString& s)
    {
        cout << "'" << s << "' ";
    }
};
int main()
{

    std::vector<myString> universe{
        "Zion", "Mars", "Unnamed", "Vader", "Unnamed"
    };

    auto newEnd = std::remove_if(universe.begin(), universe.end(),
        [](const myString& str) {
            return (str.getString() == "Unnamed");
        });

    cout << "\nAfter Removing: ";
    for_each(universe.begin(), universe.end(), viewer());

    universe.erase(newEnd, universe.end());

    cout << "\nAfter Erasing: ";
    for_each(universe.begin(), universe.end(), viewer());

    return 0;
}

The output of code:
After Removing: ‘Zion’ ‘Mars’ ‘Vader’ ‘Moved From’ ‘Unnamed’
After Erasing: ‘Zion’ ‘Mars’ ‘Vader’

  • Inside the move constructor and move assignment function, the movedFrom flag is set as true after moving.
  • The function, getString returns the string if it’s not moved. Because moved-from state members are unspecified type. That’s why we shouldn’t use these members’ values.
  • We can assign new members if we want to use these members because they are valid.
  • We overloaded ostream& operator<< because want to print directly our string with checking if it’s moved or not.
  • In the main function, we have a vector named universe that collects myStrings objects.

Suppose we want to remove unnamed elements inside the vector using the std::remove_if function.

Vector’s elements are: “Zion”,”Mars”,”Unnamed”,”Vader”,”Unnamed”

We have two “Unnamed” myString objects.

After the removing operation if we want to print all elements we might see this output:
Zion – Mars – Vader -Moved From – Unnamed

The last Unnamed object is untouched. Because there is no place to move it because of the being latest element.

“Vader” is moved to the element, “Unnamed”’s position. The position of “Vader”’ is moved-from state anymore.

The illıustratation below shows these removed and the newEnd.

We get the newEnd from the remove_if functions. For erasing elements that shouldn’t be inside between newEnd and the vector, universe’s end(), We can use the erase() member function of the vector, universe. Printing all elements after erasing:

‘Zion’ ‘Mars’ ‘Vader’

Resources:
std::remove_if