本文是本人大一期间在校学习C++课程时所撰写的实验报告的摘录,由于刚上大学,刚接触计算机编程方面的相关知识,故可能会有很多不足甚至错误的地方,还请各位看官指出及纠正。
本文所涉及到的“教材”指:
电子工业出版社《C++ Primary中文版(第5版)》
如需转载或引用请标明出处。
基本知识
左值与右值
在C语言里,一般而言,左值可以用于赋值语句的左侧,而右值不能。但是在C++中,对于左值和右值的概念可以做出以下归纳:当一个对象被用作右值时,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(即对象在内存中的位置)。
在本次实验中,需要知道的是,某些函数或表达式的求值结果是一个右值而非左值。
右值引用
对象的拷贝操作在很多情况下都会发生,在其中的某些情况下,对象被拷贝之后就马上被销毁了,这时候移动对象而非拷贝对象将会大幅提高代码的运行性能。为了支持移动操作,可以使用一种新的引用类型——右值引用,即必须绑定到右值的引用,我们通过&&而不是&来获得右值引用。由于右值引用只能绑定到一个将要销毁的对象,所以我们可以自由地将一个右值引用的资源“移动”到另一个对象中。
类似任何引用,一个右值引用也只是某个对象的另一个名字而已,它可以绑定到要求转换的表达式、字面常量或是返回右值的表达式上,但是不能将一个右值引用直接绑定到一个左值上。如:
int i = 70; //i是一个左值
int& r1 = i; //正确:r1引用i
int&& rr1 = i; //错误:右值引用rr1引用i
int& r2 = i * 70; //错误:i*70返回的结果是一个右值
const int& r3 = i * 70; //正确:可以通过const将左值引用绑定到右值上
int&& rr2 = i * 70; //正确:rr2引用右值
此外,我们不能将一个左值引用绑定到这类表达式上,但可以通过const限定符将左值引用绑定到这类表达式上。
通过一个右值的右值引用作为参数,将引用对象的资源“移动”到新的对象之中,并且保证被引用的对象处于可析构的状态,移动操作就得到了实现。
右值引用和成员函数
某个类的成员函数也可以通过定义其移动版本的操作,也可以利用移动资源的方法提高运行性能。这种允许移动的成员函数通常重载两个版本:一个版本接受一个指向const的左值引用,另一个接受一个指向非const的右值引用。例如:
void push_back(const X&); //拷贝:绑定到任意类型的X
void push_back(X&&); //移动:只能绑定到类型X的可修改的右值
我们可以将能转换为类型X的任何对象传递给都一个版本的push_back,此版本从其参数拷贝数据。对于第二个版本,我们只可以传递给他非const的右值,此版本对于非const的右值是精准匹配,因此当我们传递一个可修改的右值时,编译器会选择运行这个版本,即此版本会从其参数“窃取”数据。
成员函数this的左值或右值属性可以通过在参数列表后放置一个引用限定符来定义。引用限定符可以是&或&&,分别指出this可以指向一个左值或右值。类似于const限定符,引用限定符只能用于非static的成员函数,且必须同时出现在函数的声明和定义中。若一个函数同时使用const和引用限定符,在此情况下,引用限定符必须跟在const限定符之后。
关于该程序(重载和引用函数)
本次实验的程序通过自定义一个Foo类,重载有两个版本的成员函数sorted,它们的this分别指向右值和左值,分别可以进行移动和拷贝操作。
通过简单的例子演示了左值引用和右值引用的区别。详情见代码注释部分。
示例代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Foo
{
public:
Foo sorted() && ; //函数声明:可用于可改变的右值
Foo sorted() const &; //上面函数的重载版本:可用于任何类型的Foo
private:
vector<int> data;
};
Foo Foo::sorted() && //本对象为右值,因此可以在原址上排序
{
cout << "Rvalue reference version." << endl;
sort(data.begin(), data.end());
return *this;
}
Foo Foo::sorted() const & //本对象或为const或为左值,因此不能原址排序
{
cout << "Lvalue reference version." << endl;
Foo ret(*this); //所以要先拷贝一份对象
sort(ret.data.begin(), ret.data.end()); //再进行排序
return ret; //返回的是原对象的拷贝
}
Foo& retFoo() //返回的是一个引用,该函数调用是一个左值
{
return *(new Foo());
}
Foo retVal() //返回的是一个值,该函数调用是一个右值
{
return Foo();
}
int main(void)
{
retVal().sorted(); //使用右值版本的sorted
retFoo().sorted(); //使用左值版本的sorted
system("pause");
return 0;
}