本篇博文主要记录C++ Primer第五版 13章《拷贝控制》6小节《对象移动》的相关知识点,主要回答右值引用的相关疑难点。
1. 右值引用类型
C++ Primer P471
右值引用与右值是两个不同的概念,所谓右值引用就是必须绑定到右值的引用。右值引用类型也是一种数据类型,因此可以定义右值引用类型的变量。由于右值引用只能绑定到临时对象,使用右值引用的代码可以自由地接管所引用的对象的资源。因此有个重要结论:我们不能将一个右值引用绑定到一个右值引用类型的变量上。
2. 右值是临时的,但不是不可修改的
C++ Primer P483
在旧标准中,我们没有办法阻止这种使用方式。为了维持向后兼容性,新标准库类仍然允许向右值赋值。
实际上,右值赋值分两种情况:
(1)对内置基本数据类型的赋值;(2)对类类型的赋值。
(1)对于C/C++语言的内置基本数据类型,作为右值的场合下是不能被赋值的,也不可被修改。
(2)对于类类型的右值,该右值可以被赋值。对于类类型的右值,赋值的本质是右值进行一次函数调用(operator=);不论是类类型对象的属性是左值的或是右值的,const的或非const的,类类型的对象都是可以进行函数调用的。
如上,printstring()返回一个string临时对象,是右值;但是该右值可以调用函数(operator=)做赋值操作。同理,printdemo()返回一个demo类类型的临时对象,是右值;但是demo类型的operator=是删除的函数,所以没有赋值操作对应的函数,将报错。
现在把demo类中的operator=使用编译器合成的默认函数版本,则printdemo()返回的临时对象就可以进行赋值(operator=)的操作了。
结论:
必须通过左值才能修改对象;除非这是一个class类型的对象,此时,在某些情况下也可以通过其右值修改该对象。[例:调用该对象的成员函数可以修改该对象。]
s1+s2是右值,std::string是class类型,于是,可以通过成员函数std::string::operator=修改。int类型不是class类型,所以只能通过左值修改。
3. 左值引用与右值引用的函数重载
C++ Primer P482
要特别注意:我们不能将一个右值引用绑定到一个右值引用类型的变量上。
可以看到,对于类类型对象d1,传入左值d2和右值demo(10),分别调用demo类的operarot=(demo&)和operarot=(demo&&);然而,形参demo& 和 demo&& 调用的子类赋值函数都是拷贝赋值,而非移动赋值。因为右值引用类型的形参是一个局部变量,是左值而不是右值。
4. std::move标准库函数
要想解决第3点说的问题,需要使用std::move函数。
C++ Primer P472
可以看到,使用std::move标准库函数,可以把右值引用类型的变量当作右值使用。
C++ Primer P480
5. 左值右值引用限定符
C++ Primer P483
类似const限定符,引用限定符也是用来指定this的属性的。引用限定符可以是&或&&,分别指出this可以指向一个左值或右值。类似const限定符,引用限定符只能用于(非static)成员函数,且必须同时出现在函数的声明和定义中。
可以看到,sorted()函数重载了不同的版本,左值对象调用sorted() &版本,右值临时对象调用sorted() &&版本。
正如前面第2点,第3点所述,C++允许向类类型的右值赋值。要想阻止向类类型的右值赋值,除了定义删除的赋值运算符函数(operator=() = delete),还可以限制赋值运算符函数作用于左值this.
1.赋值函数定义了重载版本,包括拷贝版本和移动版本,因此可以有 f1 = f2; f1 = Foo();.
2.拷贝版本和移动版本都使用了引用限定符,指明赋值函数(operator=)的this都是左值属性,即赋值函数的this对象只能是左值,不能是右值.因此, Foo() = Foo(10);的调用将报错.
C++ Primer P483