1. 定义:
左值:常规变量;右值:临时变量。
右值引用:右值的引用,符号:&&。
注:右值引用,如果有变量名字,也会看作为左值(这将引出forward
的定义)。
all named values (such as function parameters) always evaluate as lvalues (even those declared as rvalue references)
2. 右引用的好处
- 问题引出:重复的构造-析构
class Class{};
# case 1
a=Class();
# case 2
void fun(Class c){}
fun(Class());
- 右引用的好处:
于是定义移动构造函数、移动赋值函数等,减少右引用情况下,不必要的构造-析构过程。 - std::move():
如果我们希望某左值也能使用这个好处,且它在之后不会被用到,则可以调用std::move
将左值转换为右值。
template <class T>
typename remove_reference<T>::type&& move (T&& arg) noexcept;
std::move
无论传入左值还是右值,都是返回右值。
注:std::move
的定义需配合以下说明来理解:
C++11 standard provides special rules for reference collapsing, which are as follows:
Object & & = Object &
Object & && = Object &
Object && & = Object &
Object && && = Object &&
3. 右引用的问题
使用右引用时,一个问题出现了。有名字的量都会被作为左值;所以如果函数某参数的类型为右引用,该值在函数内部仍会作为左值来传递,这将不利于函数内的传值操作(参考 https://www.cplusplus.com/reference/utility/forward/?kw=forward)
为了让右引用值在有名字的情况下仍能被作为右值来传递,官方定义了函数std::forward
和相关的规则:
template <class T> T&& forward (typename remove_reference<T>::type& arg) noexcept;
template <class T> T&& forward (typename remove_reference<T>::type&& arg) noexcept;
在这巧妙的设计下,右引用或左引用可以被保持:
void overloaded( int const &arg ) { std::cout << "by lvalue\n"; }
void overloaded( int && arg ) { std::cout << "by rvalue\n"; }
template <class T> void fun (T&& x) {
overloaded (x); // always an lvalue
overloaded (std::forward<T>(x)); // rvalue if argument is rvalue
}
注1:需要注意这里的T!! T与x是左值还是右值无关。
注2:如果无引用的类型传入&&类型,会被视为&类型。
参考测试代码完整版:
#include <utility> // std::move
#include <iostream> // std::cout
#include <vector> // std::vector
#include <string> // std::string
void overloaded( int const &arg ) { std::cout << "by lvalue\n"; }
void overloaded( int && arg ) { std::cout << "by rvalue\n"; }
template< typename t >void forwarding( t && arg ) {
// 0. no action
overloaded( arg );
// 1. move()
overloaded( std::move( arg ) ); // conceptually this would invalidate arg
// 2. forward()
overloaded( std::forward< t >( arg ) );
}
int main () {
//=============== move() can avoid copy ==================
std::string foo = "foo-string";
std::string bar = "bar-string";
std::vector<std::string> myvector;
// 0.no action
myvector.push_back (foo); // copies
// 1.move()
myvector.push_back (std::move(bar)); // moves
std::cout<<bar<<std::endl; // no longer exist
myvector.push_back (std::move("abc-string"));// no action
// 2.false usage of forward()
myvector.emplace_back(std::forward<std::string>(foo));// false !!
std::cout<<foo<<std::endl;//no longer exist
std::cout << "myvector contains:";
for (std::string& x:myvector) std::cout << ' ' << x;
std::cout << '\n';
//================= check type of return value ==================
std::cout << std::endl;
forwarding( 5 );
std::cout << std::endl;
int x = 5;
forwarding( x );
return 0;
}
参考链接
官方:https://www.cplusplus.com/reference/utility/forward/?kw=forward
官方:https://www.cplusplus.com/reference/utility/move/
清晰的说明:https://stackoverflow.com/questions/7510182/how-does-stdmove-transfer-values-into-rvalues