Using Move semantics
Notice that the move version of setTokens takes a non-const R-value reference (indicated by the double ampersand). This kind of parameter effectively indicates that it’s safe to move data out of it. We’ll cover that more later.
Our parser class is now ready to receive moves, but that’s not the complete picture. You also need to make sure the calling code allows the move. This is what it might look like:
void main { // Populate a list with strings std::list< std::string > tokens; tokens.push_back("blah"); tokens.push_back("foo"); tokens.push_back("random-stuff"); tokens.push_back("arbitrary-string-data"); // Move the strings to the parser Parser p; p.setTokens( std::move(tokens) ); // - snip - }
As you can see, we can construct and populate the list locally as normal. The only unfamiliar part of this is the use of std::move, which tells the compiler that we’re expecting data to be moved out. If you omit this, the call will resolve to the copy version of setTokens instead of the move version.
It’s important to note that the our local tokens list (in main) will be empty after the setTokens call. This is because ownership of all its nodes will have been transferred into Parser::m_tokens. Technically, you can continue to use the local tokens list after the move has happened, because it will be left in a valid empty state. I’d typically advise against that though, as it could hinder readability.
R-value references
R-value references are a new concept in C++11, designed primarily to enable move semantics. They are denoted by a double ampersand (&&), and are named “R-value” because they represent things you would see on the Right hand side of an assignment — i.e. something you can assign from, but cannot assign to. Conventional references are now known as “L-value” references, because they represent things you can assign to (i.e. they can appear on the Left hand side of an assignment as well as the right). (The L/R value terminology actually goes back to the early days of C, but it’s the idea of a reference to an R-value which is new.)
As I mentioned above, the idea of an R-value reference in this context is that it effectively tells you it’s safe to move data out of the object. A very important consequence of this is that your R-value references need to be non-const, because a move operation needs to modify the source to leave it empty. If the source is const then it can’t be modified.
Implicit moves
There are situations where the compiler will attempt a move implicitly, meaning you don’t have to use std::move. This can only happen if a non-const movable object is being passed / returned / assigned by value, and it’s about to be destroyed automatically.
This imminent destruction of the source object is vital. If the source object will exist after the move then the compiler can’t guarantee that you won’t try to access it again later. When you use std::move to invoke an explicit move, you’re basically overriding this check.
An implicit move will often happen where the source object is temporary (e.g. instantiated inline), for example:
std::list data; // Temporary object will be implicitly moved into list: data.push_back( Widget(123) ); // Non-temporary object won’t be implicitly moved: Widget w(123); data.push_back( w );
You might also like
Buckley: The Right Word: About the Uses and Abuses of Language, including Vocabu lary;: Usage; Style & Speaking; Fiction, Diction & Dictionaries; Reviews & Interviews; a Lexicon... Book (Random House)
|