
Move, simply - davidmckenna
https://herbsutter.com/2020/02/17/move-simply/
======
rsp1984
_C++ “move” semantics are simple, but they are still widely misunderstood._

No, they aren't simple and the fact that are still widely misunderstood is
basically proof of that.

Maybe, just maybe, it has something to do with stuffing rvalue references,
perfect forwarding and the whole universal-references-template-clusterfuck
into one and the same syntax. [1]

 _The default compiler-generated move can leave behind a null sp member_

Which is precisely why std::move should be used with caution, which, in turn,
is precisely why many codebases are still avoiding it. This problem in
particular could have been avoided by not auto-generating move constructors.

I really want to like this feature of C++, but I feel its design is just so
horribly bad and confusing that I'd feel like a total d..k if I started to
force it on a team of developers (of various levels of experience) if there's
no crystal clear need for it (and let's be honest, C++ was already pretty
successful in getting shit done before there was std::move and rvalue
references).

[1]
[http://thbecker.net/articles/rvalue_references/section_01.ht...](http://thbecker.net/articles/rvalue_references/section_01.html)

~~~
DoofusOfDeath
Let me start by saying I have tremendous respect for the people who oversee
C++'s evolution. It seems like a very difficult challenge, and I believe their
intentions are pure.

But I agree completely with your point about "move" semantics. Some language
rules that seem clear to Sutter et al are insanely complicated to normal C++
developers.

Most of us could probably stay on top of C++'s rules if we spent many hours
per week on that task. But few of us have the time or interest to do that.

~~~
dragontamer
"Simple" is an overloaded English term. Its not that "Move is so simple that
you should understand it". It seems to be "Move is so simple it doesn't
actually get the job done that most people want".

------
codr7
And this is about the point where I finally gave up on C++, I just wish I
could get the time I spent learning all the rules and all their exceptions
back.

It has now morphed into a language where today's optimal code looks horribly
inefficient by yesterday's standards. Which adds another layer of uncertainty
to what was already a pretty serious mess of a language.

Calling this simple is about as silly as it gets. Thanks, but no thanks, I
have problems to solve.

~~~
richard78459
But the world is built on C++ and there are no alternatives for it. C is too
low level and Rust has garbage collector which may not be fit for the domain
C++ is suitable for. No alternatives in the domains where C++ shines.

~~~
unrealhoang
Rust has as much garbage collector as C++ has share_ptr. Put simply, it
doesn’t have.

------
dragontamer
> (Other not-yet-standard proposals to go further in this direction include
> ones with names like “relocatable” and “destructive move,” but those aren’t
> standard yet so it’s premature to talk about them.)

Herb Sutter seems to be burying the lede here. My read on Herb Sutter's post
is that the current typical methodology (as represented by the IndirectInt
example), is buggy as per the current specification.

What we all want is "destructive move". A lot of us believe that C++ move is
"destructive move", but it is not. C++ move is this slightly different,
simpler move, that isn't in fact the destructive move that we all want.

\---------

As such, Herb Sutter seems to be pushing for a "True Destructive Move" to be
included into the C++ specification. Since std::move is CLOSE to the behavior
of true-destructive move, we might as well use it if we're writing code today.
But the C++ standard should be fixed to include the concept of a real
destructive move.

\-----

At least, that's my read on things. Anyone else have thoughts? In essence,
"C++ Move is simple, perhaps too simple and it doesn't really get the job
done".

As the specification is currently written, a "moved-from" C++ object is NOT in
any special state, like it is in some other popular languages. Programmers
seem to expect a special state however (see IndirectInt).

~~~
Someone
So, what do you propose _std::move <int>_ should do?

Initializing the ‘source’ int to zero seems the only reasonable option, but it
would hurt performance.

Also, what about _std::move <T>_ in general? If T has a destructor, should
that call it on the original? Currently, deciding whether that (or doing its
equivalent) is a good idea is up to the implementer of _std::move <T>_. I
don’t see how the compiler could make that decision without sacrificing
performance in some cases.

I think that, if you want the compiler to enforce destruction of moved-from
values, you also should want the compiler to zero out (or equivalent) all
destructed values.

~~~
dragontamer
std::move has been part of the language for 9 years. It is no longer
reasonable to change std::move.

To satisfy the programmer, there probably should be a std::dmove(x), which
would be a compiler-enforced notation that would make use of x illegal after
the use of std::dmove.

std::dmove(x) would be implemented as std::move, followed by a destructor, and
then the compiler removes variable x from the scope. Pointers and references
to that variable will have undefined behavior if they are used (compiler may
find it reasonable to recycle x's memory location to another variable)

\------

std::move<int> remains the same. There's no performance benefit, or even code-
logic benefit, to zeroing out integers. For example, an integer may be a
denominator, dividand, modulus, or multiplier. In these cases, "zeroing out"
to "x = 1" makes more sense than "x=0".

Having a compiler enforced "zero" of "x=0" is arbitrary and doesn't help
anybody. Its better to let the integers remain the same value they used to be.

~~~
Someone
But the whole point of _std::move_ is to avoid calling, possibly expensive,
destructors.

The standard example is

    
    
      template<typename T>
      void swap(T& x, T& y)
      {
        T z = std::move(x);
        x = std::move(y);
        y = std::move(z);
      }
      ...
      std::string a[1000];
      ...
      std::swap(&a[2], &a[7])
    

If _std::move_ copy-constructs and destroys, that last line would do three
allocations and three frees (recoverable by a significantly advanced compiler,
but who has that?). The way _std::move_ is specced, it can do zero allocations
and zero frees.

~~~
dragontamer
A good point.

So clearly the way std::move works now is great for that situation. But
there's a 2nd situation, with Herb Sutter's IndirectInt, which calls for a
different behavior.

------
pavon
Huh, I'm sure he is correct on the intent of the spec, but it is certainly not
how our team has been interpreting the allowable state of objects after a
move. And while I understand where he is coming from in always wanting the
object to meet its invariants until it is destructed, I think this approach
will make code harder to reason about rather than easier.

I'm a huge proponent that classes should be fully configured on construction
and should stay that way until the end of their life-cycle. I really dislike
classes that are only partially configured on construction, and then you have
to call other methods to complete their initialization. Then every method in
the class has to have guard code to check whether the object has been fully
initialized. And if you really want to code defensively users of the class
also have to handle the case where an instance of that class hasn't been fully
initialized, either by checking before calling a method, or by catching
exceptions . It complicates things for both the user and the implementator of
the class, not to mention adding computational overhead on both sides of the
API.

If you require objects to be usable after they are moved from then, every
object with a move constructor will either need to do extra work during the
move to keep itself in a valid (but different) state, or it will need to
support being in a partially configured state. In some cases it will be
inexpensive to create a valid state (say change an object to point to a
preallocated singleton), but in others it would completely defeat the benefit
of doing a move to begin with, so you are back to supporting partially
configured objects.

In the end this seems like a lot of work to keep moved objects valid, when in
practice the vast majority of moved objects are implicitly moved temporaries,
that are impossible to used after they are moved. And for the cases where an
object was explicitly moved, the misconception that you shouldn't use an
object after moving it is already well ingrained enough that it rarely happens
by accident.

I can see putting in sentinels and asserts to sanity check that objects aren't
being used after move (and typically do), but I'd still prefer to treat use-
after-move as the bug, than complicate the normal uses of the object to
support an unnecessary corner-case.

Edit: Reworded a few sentences for clarity.

~~~
jstimpfle
> I really dislike classes that are only partially configured on construction,
> and then you have to call other methods to complete their initialization.

What is "partially configured" after all? IMHO this dichotomy is just a
headache brought to you by yours truly, OOP (or rather, _syntactically
enforced OOP_ ). Personally I don't want to spend the rest of my life
pondering such philosophical questions. Or dealing with all the consequences,
like constructor exceptions, forced dynamic allocation because static doesn't
work without initialization, etc, pp.

~~~
pavon
To me the question isn't a philosophical one, but a practical one. The more
states an object has, the more special cases you have to handle, the more
likely you are to get one wrong. The more methods an object has that can only
be used when the object is in a particular state, the more likely the user of
the class it to make a mistake.

~~~
jstimpfle
Sure, but states are just there, and acting like they weren't can even
_increase_ complexity. For example, RAII basically eliminates the "memory is
allocated bit uninitialized" state by means of enforcing an automatic
transition, at the cost of being no longer able to allocate many objects
sratically.

------
butterthebuddha
Move semantics are a great addition to C++, but off the top of my head, there
are two warts:

\- C++ moves are not destructive (as compared to move semantics in Rust). This
means that types must arrange for a valid "moved-from" state. This isn't a
huge issue if the type has a natural sentinel value, but not all types have
one and I often end up wrapping class members in std::optional to arrange for
one. Frankly, this is annoying, because I now have to pay the overhead of
std::optional for no good reason (and also use a C++17 compiler, which is not
always available).

\- the syntax for rvalue references and perfect forwarding references is the
same, which is the cause for much confusion, especially among beginners. I
happily used move semantics for a year before I realized that rvalue
references and perfect forwarding references are distinct concepts.

~~~
GeneralMayhem
>C++ moves are not destructive, as they are in Rust. This means that types
must arrange for a "moved-from" state

Using an object that has been moved from is only required not to lead to a
crash - the object needs to be in a "valid but unspecified state". If there's
no sentinel value, you can leave the object in pretty much any state at all,
and it's on the caller to not do anything silly before resetting it. There's
also [https://clang.llvm.org/extra/clang-tidy/checks/bugprone-
use-...](https://clang.llvm.org/extra/clang-tidy/checks/bugprone-use-after-
move.html), which will detect any such silliness.

~~~
roca
Herb Sutter's article shows why this is actually a big problem in practice. He
has an example of a class containing a guaranteed-non-null owning pointer.
Leaving an object of this class in a "valid but unspecified state" means when
you move out of it, you'll have to replace the internal pointer with a pointer
to some new allocation, the very thing move semantics is supposed to help
avoid. He suggests that such a class should simply not be moveable.

So the fact that moves are not destructive is indeed very limiting.

~~~
AnimalMuppet
Not quite. Let's suppose that I've got a pointer to a rather large array, and
that array can't be null. When I move, the moved-from then has to allocate a
new array, to satisfy the can't-be-null constraint. So I have the overhead of
the allocation. But I _don 't_ have the overhead of copying all the bytes of
the array.

~~~
unlinked_dll
>So I have the overhead of the allocation. But I don't have the overhead of
copying all the bytes of the array.

The overhead of allocation is a magnitude greater evil than copying the bytes
of the data structure the _vast_ majority of the time. Most data is small and
short lived. As well, copying bytes in memory doesn't have side effects and is
far more deterministic than allocation.

The fact the spec says non trivially-copyable data must be in some "valid but
unspecified" state means that move semantics in C++ are borderline useless,
except in contrived cases like the one you present.

~~~
AnimalMuppet
Not quite. It's the "pointer _cannot_ be null" requirement that makes it
useless here. If you allow "pointer can be null for a moved-out-of object",
then they're much more useful.

I'd say that it's the "pointer must not be null, _even for a moved-out-of
object_ " that's the "contrived" part.

------
1024core
> C++ “move” semantics are _simple_ , but they are still widely misunderstood.

To use a quote from one of my favorite movies: _you keep using that word. it
doesn 't mean what you think it means!_

Articles such as this one remind of how C++ is such a design-by-committee
language. Watching it evolve is like watching a 100 chefs in a kitchen working
on a single dish, where everyone wants the dish to taste the way _they_ like
it.

This comment is probably not directly relevant to the article, but I had to
get it off my chest.

~~~
flukus
I don't think design-by-committee is the problem, it's more the makeup of the
committee. They're all mostly academics, compiler experts, c++ experts and
trying to chase some sort of purity. The committee needs someone that
represents the mere mortals.

------
mannykannot
I get the impression that the question-and-answer section is circling around
the issue of the semantic status of a moved-from variable without quite
dispatching it. Is a variable, when it is moved from and thus (if implemented
properly) in a valid though unspecified state, not semantically in the same
sort of state as a constructed but uninitialized integer, where any bit
pattern is valid, but if it happens to be zero and then used as a divisor,
will result in a fault?

Is it not the case that one of the main reasons for the language giving us the
ability to write an explicit no-argument constuctor is to address this sort of
problem on construction: it allows us to put the object in an application- or
library-defined state, thus establishing a convention that helps with correct
use? And is it not a common idiom, when writing a move constructor for a class
that has an explicit no-argument constructor, to leave the moved-from object
in the same state as if it were newly constructed without arguments? (e.g.
std::mutex, where that state is unlocked.) (Update: another common idiom is to
swap source and destination.) These are the some of the conventions that make
an explicit move robust.

~~~
mark-r
C++ is designed to be low overhead. Requiring a moved-from object to take on a
newly-constructed state will add overhead that won't be necessary most of the
time. If you want to add your own convention you're free to do so, knowing
what the downside will be.

~~~
mannykannot
Indeed, though it should at least go through destruction without undesirable
side-effects. In the article, however, Herb Sutter appears to be arguing for
something stronger:

 _Q: Does “but unspecified” mean the only safe operation on a moved-from
object is to call its destructor?_

 _A: No._

...

 _Q: What about objects that aren’t safe to be used normally after being moved
from?_

 _A: They are buggy..._

~~~
mark-r
Surely he doesn't consider unique_ptr to be buggy, so there must be some
subtlety that isn't captured by that statement. Perhaps it's in the definition
of "normally" \- you can't dereference a moved-from unique_ptr, but you can
certainly reassign it to a new pointer and go on to use it from there.

~~~
mannykannot
I think so, hence my opening sentence. I think it is also fair to say that
library-writers generally have less leeway in this matter than application
writers, who may know that certain scenarios are not a problem.

BTW, I have updated my original post to mention swapping source and
destination, another common idiom for satisfying the requirement.

------
favorited
As someone who only does a little C++, the advice of "that’s pretty much the
only time you should write std::move" seems overly simplistic when I read
things like this[0] from over the weekend.

The gist seems to be that newer standards simplify move semantics, so GCC
introduced a warning when you write `return std::move(result);` because the
manual move is redundant (and could actually slow down your code).

However, if you need your code to run on older compilers, you can get hard-
errors by, for example, older versions of GCC not binding the move constructor
on a return.

Also, because not all compilers have implemented the newer move semantics, you
could get copies rather than moves even on newer compilers.

So while I'm sure "only call std::move in this one circumstance" will be great
advice one day, in the real world at the moment the situation seems decidedly
more complex.

[0][http://lists.llvm.org/pipermail/cfe-
dev/2020-February/064662...](http://lists.llvm.org/pipermail/cfe-
dev/2020-February/064662.html) (mid-thread)

------
gpu_explorer
I found it's much easier to understand the concepts behind move operations if
you write an object that implemented move semantics by itself.

    
    
      class Object
      {
         int* data = new int[32];
         void move_into( Object& object )
         {
            object.data = data;
            data = nullptr;
         }
         ~Object()
         {
            delete[] data;
         }
      }
    
      Object a;
      Object b;
      a.move_into( b );
    

I think much of the confusion arises from wondering where is the allocation
for int* data located? It's somewhere on the heap. What if the data member
were int data[32] instead? What would the class look like? What would 'a' look
like afterwards?

~~~
earenndil
Don't you have to destroy the target object, before you move into it?

~~~
gpu_explorer
In this example no because of pointer. Where is the allocation for int* data
located? It's not inside the object. You can think easily of the state of 'a'
after calling move_into.

But maybe with int data[32] as a data member this is harder to reason about.

This is a way to illustrate move semantics and confusion behind what happens
to the 'moved from' object.

~~~
AnimalMuppet
You made the moved-to pointer point to the moved-from allocated heap data. Now
nothing points to the heap data that was initially allocated by the moved-to
object. So if you don't delete[] the moved-to data pointer, then you leaked 32
bytes of heap.

~~~
gpu_explorer
What happens when the program exits?

~~~
AnimalMuppet
Then it doesn't matter. It matters if you run out of heap _before_ the program
exits, though.

------
LessDmesg
C++: a language where you need 10 friggin lectures just to learn to pass
variables around. I'm sincerely sorry for anyone who is forced to use this
mountain of flaws of a language out of legacy reasons. We as humanity need a
plan to reduce the number of C++ lines being written, then to phase out C++
code altogether. This sick roadshow by the ISO committee (and that numbskull
Stroustrup) of adding features without fixing any bugs has got to stop. C++ is
just horrible, and using it is a waste of time.

------
gumby
Why is an explicit std::move required to pass a named object a as an argument
to a && ? Shouldn’t the function’s signature tell the compiler that move is
required?

~~~
pavlov
I think the reason for std::move is that the && function typically has a plain
& counterpart (i.e. plain copy). Without std::move, the lvalue (named object)
ends up calling that override.

------
_wldu
_" About the same time we were starting to work on Go, I read, or tried to
read, the C++0x proposed standard and that was convincing to me."_ \-- Ken
Thompson

Source -- [https://www.youtube.com/watch?v=sln-
gJaURzk](https://www.youtube.com/watch?v=sln-gJaURzk)

~~~
millstone
Go has it bad - the bread-and-butter append() may or may not alias the
original array. Even C++ balks at non-deterministic aliasing.

------
eej71
Nicolai Josuttis is writing a (not yet completed) book to it. I plan to read
it. Maybe then it can be "simple".

[http://www.cppmove.com/](http://www.cppmove.com/)

------
AareyBaba
This 20min Chrome Dev video goes over the same topic
[https://youtu.be/UNJrgsQXvCA](https://youtu.be/UNJrgsQXvCA)

~~~
typon
It's hilarious to me that the guy working on the Chrome performance
optimization team and presumably a world-class C++ expert still has trouble
with some questions about how the language semantics in certain cases. How do
they expect normal people to use this language?

------
pornel
This is what I like about Rust: there are no copy constructors. There are no
"moved-from" object values. Moves are guaranteed to be nothing more than a
shallow `memcpy`. Structs aren't even copyable unless the author explicitly
says so, so if a move compiles, I know it's safe and efficient.

~~~
richard78459
But the language is garbage collected, that's not desirable in the domain c++
operates

~~~
Joker_vD
Rust completely dropped GC support in 0.12 — which was released on 9th
October, 2014. That happened more than 5 years ago.

------
richard78459
On a slightly different note, why is the standard not free. I mean not the
C++20 but atleast the C++11 standard should be available freely as a single
authoritative source.

------
p2t2p
There is a̵n̵ ̵a̵p̵p̵ Rust for that.

------
titzer
It's 2020. We have planetary scale computers that do exaflop computations and
AIs that can slaughter humans at almost any strategy game. The entire stock
market is essentially driven by AI. Drugs are being developed by AI. Bitcoin
is consuming more electricity than Ireland.

And yet programmers are still fucking around with move constructors, copy
semantics, and host of other horseshit foisted upon us by people who think
that saving a copy of a couple words of memory here and there should be
absolutely _foremost_ in programming. And then one of those designers has the
gall to tell us we are all stupid for not understanding this and blowing our
legs off every day.

C++ is such a waste of everyone's time. Don't even get me started on how much
computational power its build system wastes.

 _sigh_

~~~
jcelerier
> exaflop computations

Most certainly written in c++

> and AIs that can slaughter humans at almost any strategy game.

Guess in what language is trnsorflow written

> Bitcoin

Ditto

~~~
titzer
> Most certainly written in c++

Totally beside the point!

Actually, FORTRAN mops the floor with C++ when it comes to bigtime numerical
computations on supercomputers. And they're _all_ running kernels written in
good ole C.

Most of the FAANGs are running absolute shittons of Java, Javascript, PHP, and
Python. Also running kernels written in C.

> Guess in what language is trnsorflow written

Derp, tensorflow has to interact with GPU drivers which are all C++
interfaces. Btw it generates shader code which is definitely not C++, but some
weird vendor languages, which don't have any of this move constructor related
garbage.

> Bitcoin

Ditto

Actually the vast majority of bitcoin's computational load is done by ASICs.

Congratulations, you missed the whole point. My point wasn't what languages
those things are written in (your Red Herring, not mine), but the fact that we
spend an absolute metric _shitton_ of computation on other things, but still
expect slow, dumb programmers to fret over niggling details, _usually screwing
it up in the process_ , when this task would be far better served by throwing
some god damn computational resources at it--you know the ones we waste on
O(N^2) compilation tasks and parsing header files over and over and over
again....

 _sigH_

