Why does the C++ standard require elements to be move assignable (MoveAssignable) when using for example std::vector::erase
?
For performance, and for exception-safety.
Why does one not use in-place construction (a.k.a. placement new) using the move constructor (MoveInsertable) for the elements that need to shift positions as is the case when adding elements to the std::vector exceeds the capacity of the container and internally a new and larger block of memory is allocated? Conceptually (imho), adding elements to and removing elements of a std::vector seem to be dual, but very similar operations with regard to the container management.
No, not really.
When you remove an element you don't need to create any new objects. You are strictly decreasing the number of elements, so calling a constructor is not necessary. When you add elements, obviously you need to construct objects (at least the new ones you insert, and if the vector reallocates then you need to construct every element in the new location).
Could therefore, someone clarify and explain the motivations behind the not-so-very similar operations actually used (std::vector::erase's MoveAssignable requirement vs. std::vector::emplace_back's MoveInsertable requirement)?
You could implement erasure by destroying all the elements that get affected, but doing so can be less efficient. Consider a vector<X>
where X
manages a large block of memory, and only supports copying, not efficient moving. If you destroy each one and recreate a new object at the same location with placement new then the memory already owned by each element gets deallocated, then new memory reallocated again. If the block of memory owned by each X
is the same size this is extremely wasteful: for every element you deallocate memory, then allocate exactly the same amount again. If you implement it with assignment then there is no reallocation: the element already has the required amount of storage, so it can just copy the data from one element to the next, into the memory it already has.
You're also running a destructor and a constructor for each element, rather than just an assignment operator.
But more importantly, if constructing the new element fails by throwing an exception, then you are left with a "hole" in the middle of the vector. You've destroyed an element, but not constructed a new one in its place. This breaks the invariant of the container. If you use assignment instead there is never a hole in the vector containing a dead object. If the assignment throws then the source and the target are both still valid objects, because no destructor was run yet.
Imho, the MoveAssignable requirement is quite severe, since for most objects I do not see the need for assignment operators (copy or move) (when not considering the possibility of adding these objects to a std::vector).
I would say that in general, absent any other requirements, your types should be assignable (concrete types should be regular). That's certainly true for "value types" that can be stored in containers. So if you want to use your types in standard containers, they need to model the necessary operations.
Assignability is not some esoteric weird property, it should be a default behaviour that most objects provide. Types that are not "regular" should be the exception, not the norm.
Furthermore, this also seems to deprecate the usage of const member variables (which imposes both constness from the perspective of the class interface and the internal class implementation as opposed to just const accessor member methods which can only impose constness from the perspective of the class interface and not from the internal class implementation).
How do const member functions not impose constness on the data members?