• Perfect forwardingmakes it possible to write function templates that take arbitrary arguments and forward them to other functions such that the target functions receive exactly the same arguments as were passed to the forwarding functions.
Rvalue references are the glue that ties these two rather disparate features together. They're the underlying language mechanism that makes both move semantics and perfect forwarding possible.
The more experience you have with these features, the more you realize that your initial impression was based on only the metaphorical tip of the proverbial iceberg. The world of move semantics, perfect forwarding, and rvalue references is more nuanced than it appears. std::movedoesn't move anything, for example, and perfect forwarding is imperfect. Move operations aren't always cheaper than copying; when they are, they're not always as cheap as you'd expect; and they're not always called in a context where moving is valid. The construct “ type &&” doesn't always represent an rvalue reference.
No matter how far you dig into these features, it can seem that there's always more to uncover. Fortunately, there is a limit to their depths. This chapter will take you to the bedrock. Once you arrive, this part of C++11 will make a lot more sense. You'll know the usage conventions for std::moveand std::forward, for example. You'll be comfortable with the ambiguous nature of “ type &&”. You'll understand the reasons for the surprisingly varied behavioral profiles of move operations. All those pieces will fall into place. At that point, you'll be back where you started, because move semantics, perfect forwarding, and rvalue references will once again seem pretty straightforward. But this time, they'll stay that way.
In the Items in this chapter, it's especially important to bear in mind that a parameter is always an lvalue, even if its type is an rvalue reference. That is, given
void f( Widget&& w);
the parameter wis an lvalue, even though its type is rvalue-reference-to- Widget. (If this surprises you, please review the overview of lvalues and rvalues that begins on page 2.)
Item 23: Understand std::moveand std::forward.
It's useful to approach std::moveand std::forwardin terms of what they don't do. std::movedoesn't move anything. std::forwarddoesn't forward anything. At runtime, neither does anything at all. They generate no executable code. Not a single byte.
std::moveand std::forwardare merely functions (actually function templates) that perform casts. std::move unconditionally casts its argument to an rvalue, while std::forwardperforms this cast only if a particular condition is fulfilled. That's it. The explanation leads to a new set of questions, but, fundamentally, that's the complete story.
To make the story more concrete, here's a sample implementation of std::movein C++11. It's not fully conforming to the details of the Standard, but it's very close.
template // in namespace std
typename remove_reference::type&&
move(T&& param) {
using ReturnType = // alias declaration;
typename remove_reference::type&& // see Item 9
return static_cast(param);
}
I've highlighted two parts of the code for you. One is the name of the function, because the return type specification is rather noisy, and I don't want you to lose your bearings in the din. The other is the cast that comprises the essence of the function. As you can see, std::movetakes a reference to an object (a universal reference, to be precise — see Item 24) and it returns a reference to the same object.
The “ &&” part of the function's return type implies that std::movereturns an rvalue reference, but, as Item 28explains, if the type T happens to be an lvalue reference, T&&would become an lvalue reference. To prevent this from happening, the type trait (see Item 9) std::remove_referenceis applied to T, thus ensuring that “ &&” is applied to a type that isn't a reference. That guarantees that std::movetruly returns an rvalue reference, and that's important, because rvalue references returned from functions are rvalues. Thus, std::movecasts its argument to an rvalue, and that's all it does.
As an aside, std::movecan be implemented with less fuss in C++14. Thanks to function return type deduction (see Item 3) and to the Standard Library's alias template std::remove_reference_t(see Item 9), std::movecan be written this way:
template // C++14; still in
decltype(auto)move(T&& param) // namespace std
{
using ReturnType = remove_reference_t&&
return static_cast(param);
}
Easier on the eyes, no?
Because std::movedoes nothing but cast its argument to an rvalue, there have been suggestions that a better name for it might have been something like rvalue_cast. Be that as it may, the name we have is std::move, so it's important to remember what std::movedoes and doesn't do. It does cast. It doesn't move.
Of course, rvalues are candidates for moving, so applying std::moveto an object tells the compiler that the object is eligible to be moved from. That's why std::movehas the name it does: to make it easy to designate objects that may be moved from.
In truth, rvalues are only usually candidates for moving. Suppose you're writing a class representing annotations. The class's constructor takes a std::stringparameter comprising the annotation, and it copies the parameter to a data member. Flush with the information in Item 41, you declare a by-value parameter:
class Annotation {
public:
explicit Annotation( std::stringtext); // param to be copied,
… // so per Item 41,
}; // pass by value
But Annotation's constructor needs only to read text's value. It doesn't need to modify it. In accord with the time-honored tradition of using constwhenever possible, you revise your declaration such that textis const:
class Annotation {
public:
explicit Annotation( conststd::string text)
…
};
To avoid paying for a copy operation when copying textinto a data member, you remain true to the advice of Item 41and apply std::moveto text, thus producing an rvalue:
class Annotation {
public:
explicit Annotation(const std::string text)
: value( std::move(text)) // "move" text into value; this code
{ … } // doesn't do what it seems to!
…
private:
std::string value;
};
This code compiles. This code links. This code runs. This code sets the data member valueto the content of text. The only thing separating this code from a perfect realization of your vision is that textis not moved into value, it's copied . Sure, textis cast to an rvalue by std::move, but textis declared to be a const std::string, so before the cast, textis an lvalue const std::string, and the result of the cast is an rvalue const std::string, but throughout it all, the constness remains.
Читать дальше