The move copy ctor and move assignment operator give us a idea how
to improve function interface design. By now, you imagine you have a
fun, then you have three operations inside, 1) read 2) copy 3) write.
For read, "const type&" is enough, For write, "type&" is good, because I
don’t need to write sth into prvalue. so:
For copy, thing become interesting. because for rvalue, we can move directly,
and at the same time, we have ROV.
lvalue
rvalue
copyFun(Foo foo)
copy parameter
move inside
move parameter
move inside
copyFun(Foo &)
copy inside
(NOT support)
copyFun(const Foo &)
copy inside
copy inside
copyFun(Foo &&)
(NOT support)
move inside
template<T&&>
copyFun(T &&)
copy inside
move inside
Our goal is that we use move for rvalue, so there are three options: 1)
overlaod copyFun to support const Foo& and Foo &&. 2) Use pass-value
fun(Foo ) 3) Use universal reference
Use Overload fun to deal with rvalue only. So usually function to accept
rvalue reference doesn’t exit by itself, it just stay with another version to
accept lvalue, fun(const Foo& foo);
You can use universal reference too, but inside, you have to use forward
function, and implementation is a little difficult. Detail can be seen
"effective modern c++ item 41".
As a template, implementation must typically be in a header file. It may
yield several functions in object code, because it not only instantiates
differently for lvalues and rvalues, it also instantiates differently for
std::string and types that are convertible to std::string (see Item 25). At
the same time, there are argument types that can’t be passed by
universal reference (see Item 30), and if clients pass improper argument
types, compiler error messages can be intimidating (see Item 27).
Below, there are three reasons that I have to use universal reference:
If you have a template class or template fun, universal reference
is your only choice. An example can be seen in the last chapter,
"decltype deduction" section.
First, it’s more source code to write and maintain (two functions
instead of a single template). Second, it can be less efficient.
For example, consider this use of setName: w.setName("Adela
Novak"); With the version of setName taking a universal reference,
the string literal "Adela Novak" would be passed to setName,
where it would be conveyed to the assignment operator for the
std::string inside w. w’s name data member would thus be assigned
directly from the string literal; no temporary std::string objects
would arise. With the overloaded versions of setName, however,
a temporary std::string object would be created for setName’s
parameter to bind to, and this temporary std::string would then
be moved into w’s data member.
overload has the poor scalability of the design. Widget::setName
takes only one parameter, so only two overloads are necessary, but
for functions taking more parameters, each of which could be an
lvalue or an rvalue, the number of overloads grows geometrically:
n parameters necessitates 2n overloads. Such as make_shared
function, It’s also support variadic parameter
Overloading on universal references almost always leads to the universal
reference overload being called more frequently than expected.
Perfect-forwarding constructors are especially problematic, because
they’re typically better matches than copy constructors for non-const
lvalues, and they can hijack derived class calls to base class copy and
move constructors. detail can be seen in "effective modern c++ item
26"
When you function return a value.
Apply std::move to rvalue references and std::forward to 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.