We now know how to push objects at the specific position in the array, but
there is also a way to remove them. Erase function will delete the element
at the specific index, but we also have to provide this position by using
the begin iterator. I want to erase the first element, so I will just pass
the iterator itself.
Let's compile this.
The first element had an ID of 1, and it seems that it was
successfully removed from the array.
Move assignment operator was invoked four times because these four
subjects have to be moved to the start of the array to fill in the
blank left by the erased element.
But we can also remove a range of elements by providing
the starting and ending position.
I want to start at the index of 1 and stop at the index of 3. Erase
function will remove the element at the starting index, but it will
not remove the element at the ending index.
Let's compile this.
As you can see, only the second and third element were removed from the vector
because the starting index was 1. Index 3, which defines the end of the
specified range, is not included in this deletion process.
You can see that the move assignment was invoked two times.
This is because vector moved these last two elements and
placed them on top of the deleted ones.
Now we have a way of removing elements at the specific position, but what
if we want to do something more challenging? For example, let's say that I
have this vector of integers, and I want to get rid of all of the 6s. We
could create a for loop, which will go over all of the elements, and when
it finds a 6 at the specific index, it will pass that position to the
erase function.
But there is a better way to do this.
We can use the remove function from the algorithm library.
The first two arguments specify the range,
a part of the vector that we want this remove function to operate on.
Usually, we want to do this on the whole vector, so we can
pass the begin and the end iterator.
The third argument is the value that we want to remove, in this case 6.
The remove function doesn't actually move anything. It just moves the wanted
elements over the unwanted ones, but the leftovers are still there in the
vector. The size of the vector will not change.
Let me show you what I mean.
So this is how the vector looks like at the beginning. Remove
function will start to look through the elements and check
which one of them are unwanted.
The first element is 2. It is not 6, so this one is wanted,
The second element is 7.
This one is also not 6, so nothing has to move. We want all of
the unwanted elements at the end of the array.
Now we get to the third element, which is 6. And as we know, 6 is
unwanted. But this is still okay because the unwanted number is at the
end of this part of the array we covered.
However, once we get to the number 8, which is wanted,
the unwanted number 6 is no longer at the end.
So the remove function will move this number 8 over to
number 6. Move might not be the right word.
If this was an object with move semantics, it would indeed move it.
But since these are just primitive integers,
it will actually copy the value 8 over this integer 6.
This other element with the integer of 8 is also unwanted because
this is just the leftover from the rearrangement.
So the function will treat this number just as the unwanted number 6.
The next element is 1, which is wanted.
Since this 8 is a leftover and unwanted,
we need to copy this wanted number 1 over it because
unwanted numbers always have to be at the end.
The next element is the unwanted 6.
And since this 1 is also unwanted, we don't do anything.
All of the unwanted elements are at the end.
Finally,
this number 5 is wanted, so we can make a copy of it and
put it over the last unwanted number.
And this is how the final vector looks like. If you
ignore these last two elements,
you can see that all of the wanted numbers from the original array are
correctly placed, and all of the 6s are removed. But these leftovers are
still here at the end, and we don't need them. Fortunately, the remove
function will return the iterator that points to the start of the unwanted
part of the array, in this case, the position of this number 6. We can use
this return value to pass this function as a first argument of the erase
range.
The ending iterator will be the end of the array.
So, in other words, these unwanted leftovers will be deleted
from the vector by the erase function.
This combination of remove and erase function is so common that
we call this technique erase‑remove idiom.
You use this idiom whenever you want to eliminate elements that fulfill a
certain criteria, in this case, the number 6. Since this idiom is so common,
C++ 20 provides us with another function named erase, but don't confuse this
erase function with the vector's erase function.
The erase function we used belongs to the vector class while
this new function belongs to the global scope, and it is
defined inside of the vector header.
This new function is combining the vector's erase
function and the remove function.
It basically implements the erase‑remove idiom on its own.
All we need to provide is the whole vector and then the value we
want to remove from that vector. Erase function will assume that we
want to work with the whole array, and it will first reorganize the
vector like the remove function and then erase the leftovers like
the old erase function.
This is so much easier than using two functions and
passing iterators all over the place.
Now that we know how these functions work, let's
use them on the subject's vector.
I want to work on the whole vectors, so I will pass
both iterators to the remove function.
The third argument is a value, but this is a little bit more complicated
because subjects are not a primitive data types like integers.
Fortunately, there is a complimentary remove_if function, which
does the same thing as the remove function.
But instead of passing the value you want to remove,
you pass a function that returns a boolean.
This function will be a lambda because we only need it here.
Every element from this vector will be passed down to this
function, so I will make sure that I pass it by reference
to avoid unnecessary copies.
I want to remove all of the subjects with the ID of 2.
So if a subject has the ID of 2, I will return true.
This is the list of IDs before the remove_if function.
Can you try to guess what this vector will look like after we execute the
function? Following the same logic from the previous example, we know that the
first element is wanted because it has an ID of 1.
The next element is unwanted.
But since it's at the end, we don't care. However, 3 is wanted, so we need
to call the move assignment operator to move the subject 3 over to subject
2. Since moving invalidates old subjects,
this element is left with an empty object with the ID of 0. ID 2
is unwanted, so we move on to the last element.
Since unwanted elements have to be at the end, we move assign these four over
the empty subject because that is the first unwanted subject in the array.
This invalidates the object at the last position.
So the final ID list is 1, 3, 4, 2, and 0.
I can prove this by compiling this program, and it all works as we expected.
It only needs to do two move assignments to
reorganize elements in the right way.
Now we can pass the result of this function to the vector's erase function.
But instead of doing that, let's remove this and use the new erase function.
I just need to pass in the vector and the condition presented with lambda.
When working with lambdas, we can use the complimentary erase_if function,
which combines the vector's erase and the remove_if algorithm.
And if I now compile this, you can see that the elements with
the ID of 2 are also deleted from the vector.
Okay, that is all I have to say on vectors.
If you have any questions, ask them in the discussion.