-
Notifications
You must be signed in to change notification settings - Fork 8
Reply To mcalabrese
[ I really wish github wiki had a comment facility. ]
This is a reply to https://github.com/mcalabrese/cpp-stuff/wiki/std::move-Is-Not-Destructive-Move.
First, I don't believe I have ever claimed that std::move() is destructive. I have said it is unsafe, and inefficient.
Let's define an unsafe operation. The invariant (and the only invariant) of any object is that the object is a valid representation of a value. We violate the invariant of an object within it's implementation, but generally the invariant is restored by the point we return from a public operation. Public/private/protected are C++ mechanisms that help us to define safe operations, but they are purely language mechanisms. Many languages have open classes and in C we have no such mechanisms. An unsafe operation is an operation that violates the invariant of an object and requires some additional work to restore the invariant. We use unsafe operations all the time within the implementation of our classes. There is nothing wrong with having public, unsafe operations, and at times they are required for efficiency.
The reason why I claim this is not simply a weakening of the invariant is because a moved from object is not required to represent a value. The only requirement on a moved from object is that I can assign to it, and destruct it, regardless of the wording in the standard. As I prove here https://github.com/sean-parent/sean-parent.github.com/wiki/Reply-to-Twitter-Conversation-on-Move the requirement in the standard is not achivable and therefore incorrect. This is not the same as adding a "null state" - a null state is a value, can be meaningfully copied, assigned, and compared - there is no such requirement on a moved from object. That isn't to say that the state of a moved from object can't be a null state, only that there is no requirement that it be in such a state, and imposing such a requirement would make move even less efficient.
I also don't claim to have invented this line of thinking - the idea of a partially formed type is well and rigorously described in EoP.
In other words, he's simply stated what we already know: a C++ move is a function like any other. What further can we expect? It's up to the user to decide.
That's not at all what I've said. The semantics of move construction are defined thus by the standard:
T u = rv; u is equivalent to the value of rv before the construction
Further,
rv’s state is unspecified. [Note: rv must still meet the requirements of the library component that is using it. The operations listed in those requirements must work as specified whether rv has been moved from or not. — end note ]
It is simply the note that is incorrect. rv's state is unspecified. The only operations that we can, and must, require on rv is that we can assign a valid object to it, and destruct it, because rv's state is unspecified there cannot meaningfully be any other requirements. Not coincidently, there is not a single instance in the entire standard library where any other operations are performed on a moved from object.
Swap is the perfect example:
template <class T>
void swap(T& x, T& y)
{
T tmp = move(x);
x = move(y); // assign to a moved from object
y = move(tmp); // assign to a moved from object
// tmp has been moved from and is destructed here
}
We Want Destructive Moves
Actually, I don't want destructive moves. In an ideal world we would have destructive move, which would be both more efficient and safer, and not have rvalues and the current move. An explicit move would still be necessary, and an explicit move is still potentially an unsafe operation (as is explicit destruction of a local variable - it requires that the invariant be restored before the local variable falls out of scope).
I don't believe we can turn back the clock, rvalues already add a huge amount of complexity to the current language, adding destructive moves would add significantly more complexity for a very small win. I want C++ to get simpler, not more complex, and we have much larger performance issues to tackle. We can revisit destructive move as a proposal after we are able to light-up the untapped 99% of the machine from standard C++ (see my 2012 C++Now Keynote).
Until then, I think destructive move should be kept to simply an interesting thought exercise. So in that spirit, here are some replies to your destructive move comments:
What I have intentionally left out because I'm not [yet] convinced it is correct, however, is his claim that it is safe to destructive-move from any object that is no longer explicitly referenced as deduced through some type of analysis by the compiler.
ObjC with ARC follows this model (the guarantee is actually weaker - an ARC object may be released after the last statement referencing it). My rationale here is efficiency for sink arguments.
struct some_type {
some_type(T x) : m(x) { } // I don't want this to require a write-back to x.
};
What I also left out is implicit placement-new when assigning into moved-from objects.
Okay - I was being too clever in trying to get closer to current move semantics. It was overly clever and I withdraw that section (I'll edit it out soon).
In addition to the above places where a destructive move can implicitly take place, I propose a method for explicit invocation of a destructive move:
I thought about a separate syntax for explicit destructive move - but rejected the idea because an rvalue cast is going to get it for you because of the implicit rules. If we didn't have rvalue casts already, I'd be more amenable to the idea. I also don't know that I like yours syntax because it appears as if calling the destructor is returning a value, you are reusing the same syntax for destructing the object normally and relying on the context of where it is used to sort of specialize on the return type. It is similar to an idea Alex had that we couldn't ever make work.
"Tombstones," std::kill [ and unsafe_move ] Are Not Needed
The problem I was trying to solve with std::kill() was what happens to an object during assignment if you memberwise dmove it? What is left is nothing, but you don't want to have to write back into it (that would be inefficient). But I'm reconsidering. Here is an alternative assignment operator:
some_type& operator=(some_type x) { this->~some_type(); new (this) some_type(x); // implicit dmove of x return *this; }
That would be the default implementation so you wouldn't actually have to type it. That would get rid of the need for tomestones and kill. I will remove the tomestone section and replace it with the above. Thanks for making me think more about it.
I also considered a destructive move assignment operator where the compiler would know the object, passed by reference, was consumed, but didn't like the complexity of that.