左值和右值的区分
有很多认为在等号左边的就是左值,右边的就是右值,也包括我,看了这篇文章,我想,你一定会明白的,左值是指表达式结束后依然存在的持久对象,右值是指表达式结束后依然存在的持久对象,右值是指表达式结束后就不再存在的临时对象.一个区分左值与右值的便捷方法是:看能不能对表达式取地址,如果能,则为左值,否则为右值.
int a = 12; int b = 30; int *p = &a; vector<int> vct; vctTemp.push_back(1); string str1 = "yang "; string str2 = "long"; const int &m = 10;
a和b都是持久对象(可以对其取地址),是左值。
a+b是临时对象(不可以对其取地址),是右值.
a++是先取出持久对象a的一份拷贝,再使持久对象a的值加1,最后返回那份拷贝,而那份拷贝是临时对象(不可以对其取地址),故其是右值.
++a是直接在持久对象a加1,并返回那个持久对象a(可以对其取地址).故其是左值;
vct[0]调用了重载的[],而重载了[]符号返回的类型是int &,为持久对象(可以对其取地址)。 故其是左值.
非长量左值引用和常量左值引用
非常量左值引用只能绑定到非常量左值,不能绑定到常量左值、非常量右值和常量右值,如果允许绑定到常量左值和常量右值, 则非常量左值引用可以用于修改常量左值和常量右值,这明显违反了其常量的含义,如果允许绑定到非常量右值,则会导致非常危险的情况出现,因为非常量右值是一个临时对象,非常量左值引用可能会使用一个已经被销毁了的临时对象.
常量左值引用可以绑定到所有类型的值,包括非常量左值,常量左值,非常量右值和常量右值.,可以看出, 使用左值引用时,我们无法区分出绑定的是否是非常量右值
如果我们能确定某个值是一个非常量右值(或者是一个以后不再使用的左值),则我们在进行临时对象拷贝时,可以不用拷贝实际的数据,而只是“窃取”指向实际数据的指针(类似于STL中的auto_ptr,会转移所有权).c++11中引入的右值引用正好可以用于标识一个非常量右值.
右值引用根据其修饰的不同,也可以分为非常量右值引用和常量右值引用.
非常量右值引用只能绑定到非常量右值,不能绑定到非常量左值、常量右值、和常量左值.
来看个使用移动构造函数的例子:
#include<iostream> using namespace std; class A{ private: int data; int *pi; public: //禁止隐式转换 A(){ } explicit A(int i):data(i){ cout << "normal constuctor!" << endl; pi=&data; } A(const A &a) { data=a.data; cout << "copy constuctor!"<<endl; pi=&data; } //移动构造函数; A(A &&a) { cout << "move constuctor!"<<endl; //直接移动a.pi到pi; pi=a.pi; a.pi=nullptr; a.data=0; } // A(A &&a)=delete; A operator+(const A &a) { A temp(data+a.data); cout << "operator+called!show temp!"<<endl; temp.show(); cout << endl; return temp; } void show() const { cout << "pi="<<pi<<" data=" <<data <<endl; } }; int main(int argc,char *argv[]) { int i=99; A a(10); a.show(); A b(i); b.show(); A c(b); c.show(); A d(b+c); cout << "show d!"<<endl; d.show(); return 0; }
运行结果:
normal constuctor! pi=0x7ffeba09dc10 data=10 normal constuctor! pi=0x7ffeba09dc20 data=99 copy constuctor! pi=0x7ffeba09dc30 data=99 normal constuctor! operator+called!show temp! pi=0x7ffeba09dc40 data=198 show d! pi=0x7ffeba09dc40 data=198
看运行结果,似乎没有调用移动构造函数,但是发现么.temp的pi地址和d的pi地址是一样的.原来编译器会帮我们自动优化的.我们显式的删除其移动构造函数.看程序是否能运行呢。
注释掉我们写的移动构造函数,去掉A(A&& a)=delete;的注释,运行结果为:
12.cpp: In member function ‘A A::operator+(const A&)’: 12.cpp:45:16: error: use of deleted function ‘A::A(A&&)’ return temp; ^ 12.cpp:38:5: note: declared here A(A &&a)=delete; ^ 12.cpp: In function ‘int main(int, char**)’: 12.cpp:61:12: error: use of deleted function ‘A::A(A&&)’ A d(b+c); ^ 12.cpp:38:5: note: declared here A(A &&a)=delete; ^
看结果就是说程序运行要调用A(A&& a)函数,而程序中没有这个函数。