I recently realized (pretty late in fact) that it's important to have move constructors marked as noexcept
, so that std
containers are allowed to avoid copying.
What puzzles me is why if I do an erase()
on a std::vector<>
the implementations I've checked (MSVC and GCC) will happily move every element back of one position (correct me if I'm wrong). Doesn't this violate the strong exception guarantee?
In the end, is the move assignment required to be noexcept
for it to be used by std
containers? And if not, why is this different from what happens in push_back
?
Here I am only guessing at the rationale, but there is a reason for which
push_back
might benefit more from anoexcept
guarantee thanerase
.A main issue here is that
push_back
can cause the underlying array to be resized. When that happens, data has to be moved (or copied) between the old and the new array.If we move between arrays, and we get an exception in the middle of the process, we are in a very bad place. Data is split between the two arrays with no guarantees to be able to move/copy and put it all together in a single array. Indeed, attempting further moves/copies could only raise more exceptions. Since we caa only keep either the old or the new array in the vector, one "half" of the data will simply be lost, which is tragic.
To avoid the issue, one possible strategy is to copy data between arrays instead of moving them. If an exception is raised, we can keep the old array and lose nothing.
We can also use an improved strategy when
noexcept
moves are guaranteed. In such case, we can safely move data from one array to the other.By contrast, performing an
erase
does not resize the underlying array. Data is moved within the same array. If an exception is thrown in the middle of the process, the damage is much more contained. Say we are removingx3
from{x1,x2,x3,x4,x5,x6}
, but we get an exception.(Above, I am assuming that if the move assignment fails with an exception, the object we are moving from is not affected.)
In this case, the result is an array with all the wanted objects. No information in the objects is lost, unlike what happened with resizing using two arrays. We do lose some information, since it might not be easy to spot the
<moved>
object, and distinguish the "real" data from the extraneous<moved>
. However, even in that position, this information loss is much less tragic than losing half of the vector's objects has it would happen with a naive implementation of resizing.Copying objects instead of moving them would not be that useful, here.
Finally, note that
noexcept
is still useful in theerase
case, but is it not as crucial as it is when resizing a vector (e.g.,push_back
).