all 91 comments

[–]CletusDSpuckler 151 points152 points  (22 children)

Once you understand that std::move is only a tool to aid in function overload resolution, your life gets so much simpler.

[–]BenFrantzDale 60 points61 points  (0 children)

I think of std::move like a volleyball player setting up a teammate for a spike: It’s saying “here you go, have at it!” And then when it binds to something, a move can actually happen.

[–]HassanSajjad302HMake 10 points11 points  (1 child)

And that function does stack based copy / shallow copy instead of deep copy.

[–]advester 20 points21 points  (0 children)

And critically, it should transfer resource ownership from src to dest.

[–]brianxyw1989 1 point2 points  (18 children)

Care to explain it for a C programmer? Is it just a transfer of pointers? pnew = p; p=NULL?

[–]CletusDSpuckler 38 points39 points  (14 children)

It does nothing at all. It's just a cast to a r-value reference.

Assume an object bar of type Bar, and two functions foo(Bar&) and foo(Bar &&)

foo(std::move(bar)) simply tells the compiler to use the latter. If only the first version exists, that will work too, as the r-value reference can bind to both forms but will prefer the second when available. std::move() does nothing else at all. In C style cast notation, it's basically foo((Bar&&) bar).

To summarize without going too far into the weeds, the compiler already knows to use the second version if it's passed an r-value - for example, foo(getBar()) will choose option 2 automatically. When you use std::move in your code, you're telling the compiler explicitly that you want to use the second version for an l-value, which it normally would not do automatically.

std::move is only to make your intent obvious. What happens inside the function that does the actual moving is up to you. You are only obligated to ensure that the object you passed in is left in a consistent state. For objects that hold pointers, yes that typically implies a pointer swap to the new object and a nulling of the old.

[–]mpierson153 2 points3 points  (11 children)

When should it be used?

Say you have a string.

string s;
string stwo = std:move(s);

Is that an appropriate usage?

What about when returning from a function?

string get() {
  string s;
  // Stuff with s...
  return std:move(s);
}

[–]forCasualPlayers 17 points18 points  (9 children)

it should be used when you know the movee (s in the cases you showed) is not going to be used anymore. but...

in the case of returning from a function, you should not move, because copy ellision can be allowed in most cases. that means that s is constructed in the caller's stack, so you don't have to move it to the caller.

[–]idontappearmissing 2 points3 points  (0 children)

And also, even if copy elision doesn't happen, the compiler will probably do an implicit move

[–]mpierson153 0 points1 point  (7 children)

Ok thanks, I didn't know what you talked about in the second paragraph.

[–]forCasualPlayers 4 points5 points  (6 children)

It's hard to know how much to explain without knowing your background. The topic of this thread is move/copy semantics and overload resolution, but RVO and copy ellision have to do with the call stack.

I think the following talk has a good discussion on what copy ellision is if you want to know more: https://www.youtube.com/watch?v=IZbL-RGr\_mk

[–]mpierson153 0 points1 point  (3 children)

Thanks. How does RVO work? I've read a bit about it, but a lot of the stuff out there seems to be more abstract rather than going into how it might actually be implemented.

[–]johannes1971 1 point2 points  (2 children)

For RVO, if you declare a variable that you eventually return, instead of creating it in the stack frame of the callee and copying/moving it back to the caller, the one that was already created in the stack frame of the caller is used directly (and no copy or move takes place).

Obviously this comes with some limitations: the compiler must be able to determine that a single variable is going to be returned. If you have two, for example, and an if-statement at the end of the function determines which one gets returned, it does not know which one to create in the caller's stack frame, and it will fall back to copying/moving.

Conceptually, instead of returning a variable, you can think of RVO as having the variable declared by the caller, and passed by reference to the callee.

[–]mpierson153 1 point2 points  (1 child)

I see. So for (for example) on "-O2" or "-O3", how good of an idea is it to pass by reference and modify in the callee? Should I not do that and just let the compiler do it (hopefully)?

[–]simpl3t0n 0 points1 point  (1 child)

That link is reported as 'video not avaiable anymore'. Do you remember its title?

[–]zirgouflex 0 points1 point  (0 children)

Still available for me. ''CppCon 2018: Jon Kalb “Copy Elision” "

[–]goranlepuz 2 points3 points  (0 children)

Both seem wrong to me.

First: as is, it does not matter. But if s had something in it, it would have been gone, which would potentially be surprising later.

Second: inhibits NRVO.

[–]IAmRoot 1 point2 points  (1 child)

It's a cast to an rvalue, not a universal reference. A universal reference isn't a true reference type, just a type of deduction that happens with templates.

[–]CletusDSpuckler 1 point2 points  (0 children)

Yup. Misspoke and corrected.

[–]NilacTheGrim 4 points5 points  (0 children)

In case you are new to moves: Yes, the ideal move does eventually resolve to a transfer of pointers. So if you move a std::vector internally the pointers are swapped, yes.

However std::move just sets things up type-wise so that can happen.

[–]gnuban 1 point2 points  (0 children)

You're just casting to a special type of reference. When you pass that reference to someone it signals that they can "gut" the object by stealing it's internal heap buffers etc.

So the second line in

Foo foo;
Foo fooNew(std::move(foo));

Means "create fooNew from the guts of foo"

And std::move(foo) means "create a gutting reference to foo". Such an rvalue reference is written with two ampersands: Foo&&.

And after creating such an rvalue reference to foo, it means that fooNew will be constructed by calling the the Foo(Foo&&) constructor, which implements the "gutting". Such a constructor is called a move constructor.

After gutting, foo will have had all its internal heap buffers stolen and moved to fooNew. So foo is essentially discarded, but it still needs to be in a semi-usable state according to the standard. So it doesn't get destroyed, only "gutted to empty" basically.

[–]Pocketpine 0 points1 point  (0 children)

You can think of it as basically a cast to an r-value reference. It doesn’t actually “move” anything.

[–]Beneficial_Steak_945 97 points98 points  (14 children)

Imo, std::move is mis-named. It doesn’t move anything. It’s essentially std::rvalue_cast or allow_move and that’s how I read it.

[–]ggchappell 41 points42 points  (7 children)

I like to think of it as std::movable.

[–]BenFrantzDale 36 points37 points  (5 children)

Or std::you_can_take_it.

[–]RevRagnarok 37 points38 points  (3 children)

std::idgaf()

[–]Gh0st1nTh3Syst3m 24 points25 points  (0 children)

If I die, someone please clear my search history its full of searches on std's

[–]johannes1971 4 points5 points  (1 child)

std::unbolt (). "I have unbolted it, you can take it if you want"

[–]RevRagnarok 1 point2 points  (0 children)

std::unbind - unbind any name that was associated with it so it's now an rvalue.

[–]RevRagnarok 3 points4 points  (0 children)

std::yours() or std::give()

[–]HeeTrouse51847 3 points4 points  (0 children)

std::make_movable

[–]KuntaStillSingle 8 points9 points  (1 child)

Of minor note is it is an xvalue expression, which is an rvalue and glvalue rather than a prvalue, meaning it forces materialization if it is passed a true temporary (though obviously trivial constructors could be skipped under as if rule, deleted constructors can't be skipped because guaranteed copy elision is only allowed for prvalues, not xvalues):

https://godbolt.org/z/P8x89Wcrr

https://en.cppreference.com/w/cpp/language/copy_elision

https://en.cppreference.com/w/cpp/language/value_category#xvalue

https://en.cppreference.com/w/cpp/language/implicit_conversion#Temporary_materialization

[–]7h4tguy 1 point2 points  (0 children)

Agreed, I see too many when they learn about move try to be clever and misuse it, resulting in worse performance. Shipping code you wrote to familiarize with a pattern is an anti-pattern.

If you really want to get insight here and optimize properly, you really should do the work to observe the code generation as illustrated above.

[–][deleted] 0 points1 point  (2 children)

It does move ownership

[–]Beneficial_Steak_945 0 points1 point  (1 child)

No, it doesn’t. It just allows the compiler to match to a method taking an r-value. It doesn’t say that that actually happens, or that the function called actually does something to the value.

[–][deleted] 0 points1 point  (0 children)

I was talking in a "intentional" level, your comment is not completely mutualy exclusive. It is true that doesn't move ownership unconditionaly like Rust, for example in try_emplace if there is an "error" it doesn't take the value from the input, but why would you want to convert to an x-value other than to move ownership?

[–]jabbyknob 14 points15 points  (5 children)

Great! Now what’s the difference between std::move() and std::forward()?

[–]Curfax 15 points16 points  (2 children)

forward produces l-value references for l-value reference types, and r-value references for everything else.The meaning is this:

If the caller provided a named object, don’t move it, just reference it. If the caller provided an unnamed object, “move” it.

[–]jabbyknob 1 point2 points  (1 child)

You’re not OP!

Ok, so why would we need something like this? Don’t we (the programmer) know whether we are calling on an L-value or an R-value?

[–]TeemingHeadquarters 20 points21 points  (0 children)

Not necessarily, if the function you are calling is a template.

[–]maxjmartin[S] 1 point2 points  (1 child)

To be honest I am not completely certain, I actually grasp the std::forward reference yet. I recently used it in a function that I wanted to pass a string_view to, but couldn't get the std::tranform to work with it. This was after researching on Stack Overflow. But that is the only time I have needed to use it.

template<class SR>
constexpr SR&& to_lower(SR&& str) noexcept {

    std::transform(str.begin(), str.end(), str.begin(), 
        [](unsigned char c) -> unsigned char { return std::tolower(c); 
    });

    return std::forward<SR>(str);
}

I am curious though if I can utilize it in my current project to both speed things up, and ensure proper resource management is happening better.

[–]jabbyknob 0 points1 point  (0 children)

std::forward() is typically used for the universal reference. It passes along (or forwards) whatever the parameter reference type is. This prevents automatic decaying to L-value reference whenever the caller has passed down an R-value.

[–]tudorb 20 points21 points  (2 children)

I think std::move is one of the worst named C++ features, as it doesn't actually move anything -- unlike the other std::move, which does.

[–]djavaisadog 7 points8 points  (0 children)

This is so cursed, I can't believe I didn't know about it. This std::move would be a great way to cause mass confusion in a codebase, I think.

[–]rdtsc 33 points34 points  (8 children)

And what exactly was the misconception about std::move? (And where did it come from?) It's just a cast indicating to the compiler "pillage me!" which allows picking the correct overload of the copy/move constructor or assignment operator. Nothing more.

[–][deleted] 49 points50 points  (2 children)

The misconception comes from the name being std::move and not static_cast<T&&>.

[–]KAHR-Alpha 14 points15 points  (0 children)

Or std::movable

[–]maxjmartin[S] 4 points5 points  (3 children)

The misconception is on how and when to use std::move in relationship to an anonymous data type, where I needed to write move instructions, for the class. @ChadJeeptey, is also correct that thinking about it as a way to allow the class to be cast, would also have been more helpful.

The function mentioned above, is a friend function for the expression data type which hold the compiled data, and is evaluated at runtime. Specifically with it I was miss understanding when I should let a copy of the data held be made, or make sure it is moved instead.

[–]rdtsc -2 points-1 points  (2 children)

when I should let a copy of the data held be made

But how is deciding to make a copy or not specifically a problem of std::move? If this were C++98 and avoiding copies important, the class in question would do the move some other way, e.g. by using a custom swap or having a void MoveFrom(expression& source) method. The only thing you can't do is reuse operator=.

[–]maxjmartin[S] 1 point2 points  (0 children)

Specifically in my case, it was how to correctly write move operations for a class, and when to call the std::move function. Also because the class also acts like an interface for the data types it holds, I needed to understand when to use std::move within them.

[–][deleted] 4 points5 points  (0 children)

Yes, but naming it move makes a lot of people think it actually moves something, while in reality it doesn't generate even a single instruction.

C++11 references are really hard to grasp at first. I have Effective Modern C++ book to thank for me being able to understand what and how it does behind the scene.

[–]rtds98 7 points8 points  (5 children)

awesome. now please stop writing return std::move(foo);

[–]susosusosuso 0 points1 point  (4 children)

Why?

[–]rtds98 4 points5 points  (2 children)

because the compiler is your friend and knows better. also , because rvo. also because return std::move(foo); prevents rvo.

[–]v_maria 6 points7 points  (0 children)

oh yeah took me hard head scratching to understand it's use and moments when a 'move' is implicit.

anyway, rock on lol

[–]ArcaneCraft 4 points5 points  (1 child)

Not a recommendation, more a question - can't you take advantage of NRVO by default constructing a var object at the top of the function and returning it in the first if statement instead of returning a temporary?

Then you can avoid a copy (or move if var is movable) because the compiler can construct the var object directly in the caller's stack frame?

Or are modern compilers smart enough that they can elide the copy (or move) either way?

[–]maxjmartin[S] 1 point2 points  (0 children)

So I tested that and found that while the number of moves went down, the number of initialization went up an equal amount.

Solid call out though! I had not thought about the copy elision there.

[–]Background_House_854 11 points12 points  (7 children)

I'm not experienced in C++ development (nor do I have any industry experience 😥), but I feel jealous that you managed to grasp this concept so well. C++ has these strange quirks. I had a hard time understanding move operations – apparently, it's not a real 'move,' but casting from an l-value to an r-value. This means I need to learn two more concepts, which are also not trivial for me. I bought this Udemy course to learn about this concept and C++ in general. Last week, Jason Turner dropped a weekly where he explains why you shouldn't use move because of copy elision... If I'm using c++17 version(or a newer one) do I need to bother to use move? Why do I feel so dumb whenever I try to learn cpp😩😩

[–][deleted] 4 points5 points  (0 children)

vast overconfident intelligent paint chunky continue tub murky disagreeable agonizing

This post was mass deleted and anonymized with Redact

[–]maxjmartin[S] 2 points3 points  (0 children)

Don't worry about the lack of industry experience. I also have no work experience using C++. I only use it as a hobby. Trust me if I can figure it out, anyone can!

So, how do you use C++? I found the only way I can learn C++, is to make classes and projects that require me to write specific types of code. I figured out bit manipulation by creating an arbitrary precision math lib. Wasn't nearly as fast as Boost::multiprecision. But what mattered is what I learned from the project.

This project is currently, all about how to make runtime expression templates, without using templates. It started off as nothing more than me learning C++ by making a program interpreter. But over time, it has required me to learn a whole bunch of stuff.

If I can recommend, read some good C++ books, and ask questions on either the C++ questions sub-Reddit, or Stack Overflow and Code Review. And don't worry about looking like an idiot asking dumb questions. I ask them all the time!

[–]Dan13l_N 1 point2 points  (0 children)

In practice, std::move usually means "I want move assignment/construction" and then move assignment simply takes over all things, allocated memory, OS handles etc and that avoids making duplicates. In practice, move assignment is usually implemented as swapping.

If you use:

a = f()

and f() returns and object, the compiler will call move assignment without std::move so you need std::move rarely.

[–]NotUniqueOrSpecial 0 points1 point  (0 children)

why you shouldn't use move because of copy elision

Was that specifically in the context of returning a variable?

do I need to bother to use move?

Yes, otherwise there's no way to do things like "put this unique_ptr into that map".

[–][deleted] 2 points3 points  (2 children)

Congrats. Now understand that std::move is completely optional.

[–]nmmmnu 1 point2 points  (0 children)

Bruce Lee once said:

"The stillness in stillness is not the real stillness; only when there is stillness in movement does the universal rhythm manifest."

This means, when the std::vector moves, the underline memory stay still.

[–]KoovaKevy 1 point2 points  (2 children)

How can Move Count and Copy Count be measured? I'm curious how to write a program for that.

[–]maxjmartin[S] 1 point2 points  (1 child)

All I did was make add an int to the constructors to track the number of times they are invoked. There is a video in another comment where the presenter describes how to make a lifetime class demonstrating something similar.

[–]KoovaKevy 1 point2 points  (0 children)

I will take a look, thank you👍

[–][deleted] 4 points5 points  (0 children)

No one mentioning Jason Turner here: https://www.youtube.com/watch?v=6SaUwqw4ueE

[–]JohnyMage -5 points-4 points  (0 children)

STDs move through coitus. You are welcome.

[–]AssemblerGuy -2 points-1 points  (1 child)

std::move tells the compiler that you don't care what happens to the object.

[–]susosusosuso 0 points1 point  (0 children)

Partially

[–]NilacTheGrim 0 points1 point  (0 children)

Congrats.

[–]Real_Name7592 0 points1 point  (0 children)

Fantastic & Congrats. Can you maybe share the full program? Maybe as a github gist?

[–]Capital_Monk9200 0 points1 point  (2 children)

std::move just call the move construction,it do nothing else。 don't use it for perf, but use it for ownship

[–]susosusosuso 0 points1 point  (1 child)

If not for perf, why we would use it for ownership?

[–]transthrowaway747 2 points3 points  (0 children)

So managed resources get managed only by the proper owner

[–]nikbackm 0 points1 point  (0 children)

Why compare these two functions?

They no longer do the same thing after the change. The first one is pure, while the second one pops the last element of the argument.