目录
(1)为何要区分?
引用的类型是左值/右值,指明了其绑定的对象的状态是仍有可能被使用,or不再被修改、使用甚至被销毁。而对象的状态(左值/右值)又可决定,如何“拷贝”对象所拥有的动态资源——拷贝or接管。这一操作依赖类内的拷贝/移动构造函数以及拷贝/移动赋值运算符。
下面以vector<int>举例,窥探对象的状态(左值/右值)对“拷贝”对象这一操作的影响:
#define HUGENUM 1000000000 //10亿
int main()
{
vector<int> A(HUGENUM, 50), B, C; //vector类的对象内拥有动态资源
clock_t start, end;
start = clock();
B = A; // 拷贝资源
end = clock();
cout << "time consumed on copy is " << (end - start) / CLK_TCK << endl;
start = clock();
C = std::move(A); // 移动(接管)资源
end = clock();
cout << "time consumed on move is " << (end - start) / CLK_TCK << endl;
}
运行结果如下:
可见,对于拥有动态资源的对象,其状态(左值/右值)决定了其被“拷贝”时的具体实现,而当对象拥有大量的动态资源时,这一实现会影响到运行效率。
(2)那什么是左值/右值引用?
形式上,定义引用时有一个&修饰符的则为左值引用,有两个&修饰符的则为右值引用。左值/右值引用必须绑定对应状态的对象,如下:
---- 右值引用只能绑定到右值对象上;
---- 非const左值引用只能绑定到左值对象上,而const左值引用对于左值/右值对象均能绑定。(const左值引用无法修改其绑定的对象,这符合右值的设计理念)
(3)左值?右值?
左值可位于赋值号的左侧和右侧,可寻址,其值既能被使用也能被修改。而右值只能位于赋值号右侧,不可寻址,可使用而不可修改其值。
常见左值包括:定义的对象、引用(不论左值/右值)、赋值/下标/解引用/前置自增自减等运算符返回的值,以及返回类型为引用类型的函数所返回的值;
常见右值包括:字面值常量、算数/关系/逻辑/位等运算返回的值、后置自增自减运算符返回的值,以及返回类型为非引用类型的函数所返回的值。
(4)左值/右值,与左值/右值引用有什么区别?
---- 左值/右值引用本身属于左值,既能使用也能修改其绑定的对象的值。但我们在使用右值引用时,还是应该遵循右值的设计理念,即假设右值引用绑定的对象不会再被修改、使用,甚至可能立刻被销毁;
---- 右值引用绑定的既可能是真实的右值如(3)所述,也可能是我们希望是右值但实际并不是的普通对象(利用std::move()转换)。