C++中的左值,右值,左值引用,右值引用
左值与右值(lvalue&rvalue)
左右值来源于C语言,原意是为了方便记忆:左值可以位于赋值语句的左侧,右值则不能。但是在C++中,左右值的区分没有那么简单,还存在 许多例外的情况,如:
- 一个左值表达式的求值结果是一个对象或一个函数,然而以常量对象为代表的某些左值实际上是不能作为赋值语句的左侧运算对象的。如:
const int a =1; //a是左值但不可位于赋值语句左侧
- 某些表达式的求值结果是对象,但它们是右值而非左值。
那么,到底如何区分左右值,我的理解是,一个左值表达式首先必须要能使用其地址,所以不能是常量表达式,计算式,字面常量。可以简单归纳为:
- 左值:占用了一定内存,且拥有可辨认的地址的对象。包括所有变量。
- 右值:左值以外的所有对象。如,字面量、临时对象、临时表达式等。
此外,左右值的使用可以总结出几点规律:
- 一般而言,一个左值表达式表示的是一个对象的身份(在内存中的位置),而一个右值表达式表示的是一个对象的值(内容)
- 在需要右值表达式的地方可以用左值表达式来代替(此时使用的是左值的值),但不能用右值表达式代替左值表达式。
- 一个表达式一定是左值表达式和右值表达式中的一种。
- 返回左值引用的函数,连同赋值、下标、解引用和前置递增/递减运算符,都生成左值。
- 返回非引用类型的函数,连同算术、关系、位以及后置递增/递减运算符,都生成右值。
左值引用与右值引用(lvalue reference&rvalue reference)
左值引用,就是正常的引用,需要注意的是const引用,
int i=1; int &a = i; //正确,i的左值引用 int &b = 2; //错误,左值引用不能绑定右值 const int &c = 3; // 正确,特殊情况,可以理解为临时创建了一个左值,然后引用
右值引用,是C++11引入的新类型,
int &&i = 1;
右值引用有两个主要用途:
- 移动语意(Move Semantics)
Rvalue引用支持移动语义的实现,这可以显着提高应用程序的性能。移动语义使您可以将资源(如动态分配的内存或者数据)从一个对象传输到另一个对象,减少内存分配和复制操作。移动运算符只是简单的切换右值的内部缓冲区为自己的,所以右值析构器将会释放我们对象自己不再使用的缓冲区。移动语义之所以起作用,是因为它使资源能够从程序中其他地方无法引用的临时对象中转移。要实现移动语义,通常需要为类提供一个移动构造函数以及一个可选的移动赋值运算符(operator =)。源为右值的复制和赋值操作将自动利用移动语义。与默认的复制构造函数不同,编译器不提供默认的move构造函数。
std::move就是一个将左值转化为右值的函数,例如:
template<typename T> void swap(T& a, T& b) { T t(std::move(a)); // a为空,t占有a的初始数据 a = std::move(b); // b为空, a占有b的初始数据 b = std::move(t); // t为空,b占有a的初始数据 }
- 完美转发(Perfect Forwarding)
完美转发减少了重载函数,避免了转发的问题。转发的问题出现在你写通用函数将引用作为参数,将这些参数由函数调用的时候。举个例子,如果通用函数将 type const T&作为参数,那么调用函数不能修改参数的值。如果通用函数将 type T&作为参数,那么当参数是右值的时候,函数不能调用。通常来说,为了解决上述的问题,你需要提供重载函数,既要有type const T&参数的函数,也要有type T&参数的函数。结果呢,重载函数的数量随着参数数量呈指数递增。而右值引用能够使你只用一个函数就能适用于任意数量的参数。
以下是不使用完美转发的代码:
#include<iostream> #include<string> using namespace std; template<typename T> struct S; template<typename T> struct S<T&> { static void print(T& t) { cout << "print<T&>: " << t << endl; } }; template<typename T> struct S<const T&> { static void print(const T& t) { cout << "print<const T&>: " << t << endl; } }; template<typename T> struct S<T&&> { static void print(T&& t) { cout << "print<T&&>: " << t << endl; } }; template <typename T> struct S<const T&&> { static void print(const T&& t) { cout << "print<const T&&>: " << t << endl; } }; template<typename T> void print_type_and_value(T&& t) { S<T&&>::print(std::forward<T>(t)); } const string fourth() { return string("fourth"); } int main() { string s1("first"); print_type_and_value(s1);//左值 const string s2("second"); print_type_and_value(s2);//const 左值 print_type_and_value(string("third"));//右值 print_type_and_value(fourth());//const 右值 return 0; }
然后是使用了完美转发后的代码:
#include<iostream> #include<string> using namespace std; template <class T> void print(T&& t) { cout << "success! " <<t<< endl; } string fourth() { return string("fourth"); } int main() { string s1("first"); print(s1);//左值 const string s2("second"); print(s2);//const 左值 print(string("third"));//右值 print(fourth());//const 右值 return 0; }
- 同时接受左值和右值作为参数
#include<iostream> using namespace std; void Print(const string &s){ cout<<s; } int main(){ string s="abc"; Print(s);//s为左值 Print("abc");//"abc"为右值 }
#include<iostream> using namespace std; class Demo{}; void Func(const Deno &){ cout<<"Func(const Demo &)"<<endl; } void Func(Demo &){ cout<<"Func(Demo &)"<<endl; } void Func(Demo &&){ cout<<"Func(Demo &&)"<<endl; } int main(){ Demo d; Func(d);//左值 Func(Demo());//右值 const Demo cd; Func(cd);//左值 }