Widget makeWidget() // as before
{
Widget w;
…
return w;
}
compilers must either elide the copying of wor they must treat the function as if it were written like this:
Widget makeWidget() {
Widget w;
…
return std::move(w); // treat w as rvalue, because
} // no copy elision was performed
The situation is similar for by-value function parameters. They're not eligible for copy elision with respect to their function's return value, but compilers must treat them as rvalues if they're returned. As a result, if your source code looks like this,
WidgetmakeWidget( Widget w) // by-value parameter of same
{ // type as function's return
…
return w;
}
compilers must treat it as if it had been written this way:
Widget makeWidget(Widget w) {
…
return std::move(w); // treat w as rvalue
}
This means that if you use std::moveon a local object being returned from a function that's returning by value, you can't help your compilers (they have to treat the local object as an rvalue if they don't perform copy elision), but you can certainly hinder them (by precluding the RVO). There are situations where applying std::moveto a local variable can be a reasonable thing to do (i.e., when you're passing it to a function and you know you won't be using the variable any longer), but as part of a returnstatement that would otherwise qualify for the RVO or that returns a by-value parameter isn't among them.
Things to Remember
• Apply std::moveto rvalue references and std::forwardto universal references the last time each is used.
• Do the same thing for rvalue references and universal references being returned from functions that return by value.
• Never apply std::moveor std::forwardto local objects if they would otherwise be eligible for the return value optimization.
Item 26: Avoid overloading on universal references.
Suppose you need to write a function that takes a name as a parameter, logs the current date and time, then adds the name to a global data structure. You might come up with a function that looks something like this:
std::multiset names; // global data structure
void logAndAdd(const std::string& name) {
auto now = // get current time
std::chrono::system_clock::now();
log(now, "logAndAdd"); // make log entry
names.emplace(name); // add name to global data
} // structure; see Item 42
// for info on emplace
This isn't unreasonable code, but it's not as efficient as it could be. Consider three potential calls:
std::string petName("Darla");
logAndAdd( petName); // pass lvalue std::string
logAndAdd( std::string("Persephone")); // pass rvalue std::string
logAndAdd( "Patty Dog"); // pass string literal
In the first call, logAndAdd's parameter nameis bound to the variable petName. Within logAndAdd, nameis ultimately passed to names.emplace. Because nameis an lvalue, it is copied into names. There's no way to avoid that copy, because an lvalue ( petName) was passed into logAndAdd.
In the second call, the parameter nameis bound to an rvalue (the temporary std::stringexplicitly created from "Persephone"). nameitself is an lvalue, so it's copied into names, but we recognize that, in principle, its value could be moved into names. In this call, we pay for a copy, but we should be able to get by with only a move.
In the third call, the parameter nameis again bound to an rvalue, but this time it's to a temporary std::stringthat's implicitly created from "Patty Dog". As in the second call, name is copied into names, but in this case, the argument originally passed to logAndAddwas a string literal. Had that string literal been passed directly to emplace, there would have been no need to create a temporary std::stringat all. Instead, emplacewould have used the string literal to create the std::stringobject directly inside the std::multiset. In this third call, then, we're paying to copy a std::string, yet there's really no reason to pay even for a move, much less a copy.
We can eliminate the inefficiencies in the second and third calls by rewriting logAndAddto take a universal reference (see Item 24) and, in accord with Item 25, std::forwarding this reference to emplace. The results speak for themselves:
template
void logAndAdd( T&&name) {
auto now = std::chrono::system_clock::now();
log(now, "logAndAdd");
names.emplace( std::forward(name ));
}
std::string petName("Darla"); // as before
logAndAdd( petName); // as before, copy
// lvalue into multiset
logAndAdd( std::string("Persephone")); // move rvalue instead
// of copying it
logAndAdd( "Patty Dog"); // create std::string
// in multiset instead
// of copying a temporary
// std::string
Hurray, optimal efficiency!
Were this the end of the story, we could stop here and proudly retire, but I haven't told you that clients don't always have direct access to the names that logAndAddrequires. Some clients have only an index that logAndAdduses to look up the corresponding name in a table. To support such clients, logAndAddis overloaded:
std::string nameFromIdx(int idx); // return name
// corresponding to idx
void logAndAdd( int idx) // new overload
{
auto now = std::chrono::system_clock::now();
log(now, "logAndAdd");
names.emplace( nameFromIdx(idx));
}
Resolution of calls to the two overloads works as expected:
std::string petName("Darla"); // as before
logAndAdd(petName); // as before, these
logAndAdd(std::string("Persephone")); // calls all invoke
logAndAdd("Patty Dog"); // the T&& overload
logAndAdd(22); // calls int overload
Actually, resolution works as expected only if you don't expect too much. Suppose a client has a shortholding an index and passes that to logAndAdd:
short nameIdx;
… // give nameldx a value
logAndAdd(nameldx); // error!
The comment on the last line isn't terribly illuminating, so let me explain what happens here.
Читать дальше