一、左值与右值
左值 与 右值是C++中表达式的属性,在C++11中,每个表达式有两个属性: 类型(type,除去引用特性,用于类型检查)和 值类型(value category,用于语法检查,比如一个表达式结果是否能被赋值)。
值类型包括3个基本类型:lvalue、prvalue与xrvalue,后两者又统称为rvalue,lvalue我们称为左值,可以将左值看成是一个可以获取地址的量,它可以用来标识一个对象或函数,rvalue称为右值,可以认为所有不是左值的量就是右值,prvalue就是纯粹的右值,比如字面量,xrvalue指的是可以被重用的临时对象。
二、右值引用
在C++98中,临时量( 术语为右值,因其出现在赋值表达式的右边 )可以被传给函数,但只能被接受为const &类型。这样函数便 无法区分传给const &的是真实的右值还是常规变量,而且,由于类型为const &,函数也无法改变所传对象的值
在C++11引入右值引用,记作typename &&,这种类型 可以被接受为非const值,从而允许改变其值
右值引用 是C++11引入的一个非常重要的技术,因为它是移动语义(Move semantics)与完美转发(Perfect forwarding)的基石:
移动语义:将内存的所有权从一个对象转移到另外一个对象,高效的移动用来替换效率低下的复制,对象的移动语义需要实现移动构造函数(move constructor)和移动赋值运算符(move assignment operator)
#include <iostream> #include <string> #include <utility> int main () { std::string &&rr = "World"; { std::string s = "Hello"; rr = std::move(s ); // rr获取s的资源 //把一个左值移到为右值引用 s = "!!!"; //std::cout << s; /* 请注意,移动意味着被移出的对象保持在有效但未指定的状态。 这意味着,在这样一个操作之后,移出对象的值只应该被销毁或赋一个新值; 否则访问它将产生一个未指定的值。 */ } std::cout << rr; return 0; }
// move example #include <utility> // std::move #include <iostream> // std::cout #include <vector> // std::vector #include <string> // std::string int main () { std::string foo = "foo-string"; std::string bar = "bar-string"; std::vector<std::string> myvector; myvector.push_back (foo); // copies myvector.push_back (std::move(bar)); // moves std::cout << "myvector contains:"; for (std::string& x:myvector) std::cout << ' ' << x; std::cout << '\n'; return 0; }
完美转发:定义一个函数模板,该函数模板可以接收任意类型参数,然后将参数转发给其它目标函数,且保证 目标函数接受的参数其类型与传递给模板函数的类型相同。
// forward example #include <utility> // std::forward #include <iostream> // std::cout // function with lvalue and rvalue reference overloads: void overloaded (const int& x) {std::cout << "[lvalue]";} void overloaded (int&& x) {std::cout << "[rvalue]";} // function template taking rvalue reference to deduced type: template <class T> void fn (T&& x) { overloaded (x); // always an lvalue overloaded (std::forward<T>(x)); // rvalue if argument is rvalue } int main () { int a; std::cout << "calling fn with lvalue: "; fn (a); //如果参数a是左值引用,则该函数返回类型不变的a // [lvalue][lvalue] std::cout << '\n'; std::cout << "calling fn with rvalue: "; fn (0); //否则,函数返回一个右值引用(T&&),该引用指向可用于传递右值的参数//[lvalue][rvalue] std::cout << '\n'; return 0; }
#include <utility> #include <iostream> #include <type_traits> //这个头文件定义了一系列在编译时获取类型信息的类 /* void overloaded_fn(int& x){ //左值引用 x = 111; } void overloaded_fn(int&& x){//右值引用 x = 222; } */ template<typename T> void overloaded_fn(T&& x){ x = 888; } template<typename T> // 实参为左值,T 为 int& //实参为右值,T为int void f( T&& x){ //T 为 int&,函数参数引用折叠//int& && int& //is_same是标识T是否与U相同类型的Trait类 if(std::is_same<T, int& >::value ) std::cout << "int&"; if(std::is_same<T, int >::value ) std::cout << "int"; overloaded_fn(std::forward<T>(x) ); //左值转发左值,右值转发右值// 完美转发 } /* using T = int&; void f( T&& x ){ //引用折叠 std::cout << "void f( int&& x)\n"; } */ int main () { int a = 333; f( a ); //f(std::move(a) ); std::cout << a; return 0; }
std::forward保持实参的类型,保持实参的左值/右值属性
#include <utility> #include <iostream> #include <type_traits> void overloaded_fn(const int& x){ //左值引用 std::cout << "void overloaded_fn(const int& x)"; } void overloaded_fn(int&& x){ //右值引用 std::cout << "void overloaded_fn(int&& x)"; } template<typename T> //实参的所有性质(T),包括实参是否是const的以及实参是左值还是右值 void f( T&& x){ //is_same是标识T是否与U相同类型的Trait类 if(std::is_same<T, int& >::value ) std::cout << "int&, "; if(std::is_same<T, const int& >::value ) std::cout << "const int&, "; if(std::is_same<T, int >::value ) std::cout << "int, "; overloaded_fn( std::forward<T>(x) ); //右值,不用forward,会匹配 void overloaded_fn(const int& x) //std::forward保持实参的类型,保持实参的左值/右值属性 } int main () { const int a = 333; f( 15 ); //右值 //f(a ); //左值 const int& return 0; }
可变参数模板与完美转发
#include <utility> #include <iostream> template<typename Func, typename... Args> void test(Func f, Args&&... args ) { f(std::forward<Args>(args)... ); } void func(int a, int &b){ a++; b++; } void func(int &a, int &b){ a++; b++; } int main () { int a=3; int b=5; using F1 = void (*)(int,int& ); using F2 = void (*)(int&,int&); F1 f1 = &func; F2 f2 = &func; //test(f1, a, b ); //传左值 //test(f1, 15, b ); //传右值、左值 test(f2, a, b ); //传左值 std:: cout << a << "," << b; return 0; }