template
void emplace_back( Args&&... args);
…
};
Here, the type parameter Args
is independent of vector
's type parameter T
, so Args
must be deduced each time emplace_back
is called. (Okay, Args
is really a parameter pack, not a type parameter, but for purposes of this discussion, we can treat it as if it were a type parameter.)
The fact that emplace_back
's type parameter is named Args
, yet it's still a universal reference, reinforces my earlier comment that it's the form of a universal reference that must be “ T&&
”. There's no requirement that you use the name T
. For example, the following template takes a universal reference, because the form (“ type &&
”) is right, and param
's type will be deduced (again, excluding the corner case where the caller explicitly specifies the type):
template // param is a
void someFunc( MyTemplateType&¶m); // universal reference
I remarked earlier that auto
variables can also be universal references. To be more precise, variables declared with the type auto&&
are universal references, because type deduction takes place and they have the correct form (“ T&&
”). auto
universal references are not as common as universal references used for function template parameters, but they do crop up from time to time in C++11. They crop up a lot more in C++14, because C++14 lambda expressions may declare auto&&
parameters. For example, if you wanted to write a C++14 lambda to record the time taken in an arbitrary function invocation, you could do this:
auto timeFuncInvocation =
[]( auto&&func, auto&&... params) // C++14
{
start timer;
std::forward(func)( // invoke func
std::forward(params)... // on params
);
stop timer and record elapsed time;
};
If your reaction to the “ std::forwardblah blah blah )>
” code inside the lambda is, “What the…?!”, that probably just means you haven't yet read Item 33. Don't worry about it. The important thing in this Item is the auto&&
parameters that the lambda declares. func
is a universal reference that can be bound to any callable object, lvalue or rvalue. args
is zero or more universal references (i.e., a universal reference parameter pack) that can be bound to any number of objects of arbitrary types. The result, thanks to auto
universal references, is that timeFuncInvocation
can time pretty much any function execution. (For information on the difference between “any” and “pretty much any,” turn to Item 30.)
Bear in mind that this entire Item — the foundation of universal references — is a lie… er, an “abstraction.” The underlying truth is known as reference collapsing , a topic to which Item 28is dedicated. But the truth doesn't make the abstraction any less useful. Distinguishing between rvalue references and universal references will help you read source code more accurately (“Does that T&&
I'm looking at bind to rvalues only or to everything?”), and it will avoid ambiguities when you communicate with your colleagues (“I'm using a universal reference here, not an rvalue reference.”). It will also allow you to make sense of Items 25and 26, which rely on the distinction. So embrace the abstraction. Revel in it. Just as Newton's laws of motion (which are technically incorrect) are typically just as useful as and easier to apply than Einstein's theory of general relativity (“the truth”), so is the notion of universal references normally preferable to working through the details of reference collapsing.
Things to Remember
• If a function template parameter has type T&&
for a deduced type T
, or if an object is declared using auto&&
, the parameter or object is a universal reference.
• If the form of the type declaration isn't precisely type &&
, or if type deduction does not occur, type &&
denotes an rvalue reference.
• Universal references correspond to rvalue references if they're initialized with rvalues. They correspond to lvalue references if they're initialized with lvalues.
Item 25: Use std::move
on rvalue references, std::forward
on universal references.
Rvalue references bind only to objects that are candidates for moving. If you have an rvalue reference parameter, you know that the object it's bound to may be moved:
class Widget {
Widget( Widget&&rhs); // rhs definitely refers to an
… // object eligible for moving
};
That being the case, you'll want to pass such objects to other functions in a way that permits those functions to take advantage of the object's rvalueness. The way to do that is to cast parameters bound to such objects to rvalues. As Item 23explains, that's not only what std::move
does, it's what it was created for:
class Widget {
public:
Widget(Widget&& rhs) // rhs is rvalue reference
: name( std::move(rhs.name)),
p( std::move(rhs.p))
{ … }
…
private:
std::string name;
std::shared_ptr p;
};
A universal reference, on the other hand (see Item 24), might be bound to an object that's eligible for moving. Universal references should be cast to rvalues only if they were initialized with rvalues. Item 23explains that this is precisely what std::forward
does:
class Widget {
public:
template
void setName(T&& newName) // newName is
{ name = std::forward(newName);} // universal reference
…
};
In short, rvalue references should be unconditionally cast to rvalues (via std::move
) when forwarding them to other functions, because they're always bound to rvalues, and universal references should be conditionally cast to rvalues (via std::forward
) when forwarding them, because they're only sometimes bound to rvalues.
Item 23explains that using std::forward
on rvalue references can be made to exhibit the proper behavior, but the source code is wordy, error-prone, and unidiomatic, so you should avoid using std::forward
with rvalue references. Even worse is the idea of using std::move
with universal references, because that can have the effect of unexpectedly modifying lvalues (e.g., local variables):
class Widget {
public:
template
void setName( T&&newName) // universal reference
{ name = std::move(newName); } // compiles, but is
… // bad, bad, bad!
private:
std::string name;
Читать дальше