• 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::move
doesn'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::move
and 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 w
is 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::move
and std::forward
.
It's useful to approach std::move
and std::forward
in terms of what they don't do. std::move
doesn't move anything. std::forward
doesn't forward anything. At runtime, neither does anything at all. They generate no executable code. Not a single byte.
std::move
and std::forward
are merely functions (actually function templates) that perform casts. std::move unconditionally casts its argument to an rvalue, while std::forward
performs 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::move
in 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::move
takes 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::move
returns 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_reference
is applied to T, thus ensuring that “ &&
” is applied to a type that isn't a reference. That guarantees that std::move
truly returns an rvalue reference, and that's important, because rvalue references returned from functions are rvalues. Thus, std::move
casts its argument to an rvalue, and that's all it does.
As an aside, std::move
can 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::move
can 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::move
does 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::move
does and doesn't do. It does cast. It doesn't move.
Of course, rvalues are candidates for moving, so applying std::move
to an object tells the compiler that the object is eligible to be moved from. That's why std::move
has 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::string
parameter 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 const
whenever possible, you revise your declaration such that text
is const
:
class Annotation {
public:
explicit Annotation( conststd::string text)
…
};
To avoid paying for a copy operation when copying text
into a data member, you remain true to the advice of Item 41and apply std::move
to 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 value
to the content of text
. The only thing separating this code from a perfect realization of your vision is that text
is not moved into value
, it's copied . Sure, text
is cast to an rvalue by std::move
, but text
is declared to be a const std::string
, so before the cast, text
is an lvalue const std::string
, and the result of the cast is an rvalue const std::string
, but throughout it all, the const
ness remains.
Читать дальше