Search code examples
c++c++11move-semanticsmove-assignment-operator

What is the rationale for self-assignment-unsafe move assignment operators in the standard library?


The standard library policy about move assignment is that the implementation is allowed to assume that self-assignment will never happen; this seems to me a really bad idea, given that:

  • the "regular" ("copy") assignment contract in C++ has always been regarded as safe against self-assignment; now we have yet another incoherent corner case of C++ to remember and to explain - and a subtly dangerous one, too; I think we all agree that what is needed in C++ is not more hidden traps;
  • it complicates algorithms - anything in the remove_if family need to take care of this corner case;
  • it would be really easy to fulfil this requirement - where you implement move with swap it comes for free, and even in other cases (where you can get some performance boost with ad-hoc logic) it's just a single, (almost) never taken branch, which is virtually free on any CPU¹; also, in most interesting cases (moves involving parameters or locals) the branch would be removed completely by the optimizer when inlining (which should happen almost always for "simple" move assignment operators).

So, why such a decision?


¹ Especially in library code, where implementers can liberally exploit compiler-specific hints about "branch expected outcome" (think __builtin_expect in gcc/__assume in VC++).


Solution

  • Moved from objects in std are supposed to be discarded or assigned to prior to being reused. Anything that is not completely free beyond that is not promised.

    Sometimes things are free. Like a move-constructed-from container is empty. Note that some move-assiged-from cases have no such guarantee, as some implementations may choose to move elements instead of buffers. Why the difference? One was a free extra guarantee, the other not.

    A branch or other check is not completely free. It takes up a branch prediction slot, and even if predicted is merely almost free.

    On top of that, a = std::move(a); is evidence of a logic error. Assign-from a (within std) means you will only assign-to or discard a. Yet here you are wanting it to have specific state on the next line. Either you know you are self-assigning, or you do not. If you do not, you are now moving from object you are also populating and you do not know it.

    The principle of "do little things to keep things safe" clashes with "you do not pay for that which you do not use". In this case, the second won.