引用:
C++98中提出了引用的概念,引用即别名,引用变量与其引用实体共用同一块内存空间,而引用的底层是通过指针来实现,因此使用引用,可以提高程序的可读性。
右值引用(类型&& 右值):
(1)为了提高程序的运行效率,C++11中引入了右值引用;
(2)右值引用也是别名;
(3)只能对右值进行引用。
(4)右值有了名字后,就成了一个普通变量,也就是左值。
左值与右值是C语言中的概念,但C标准并没有给出严格的区分方式。一般认为:可以放在=的左边,或能够取地址的称为左值;只能放在=右边的,或不能取地址的称为右值。但不一定完全正确。
一般认为:
(1)普通类型的变量,因为有名字,可以取地址,都认为是左值。
(2)const修饰的常量,不可修改,只读类型,理论上应该按照右值对待,但因为其可以取地址,C++11认为其是左值。
(3)如果表达式的运行结果是一个临时变量或者对象,认为是右值。
(4)如果表达式运行的结果或单个变量是一个引用,认为是左值。
C++11对右值进行了严格区分:
(1)C语言中的纯右值,比如:10,a+b;
(2)将亡值,比如:表达式的中间结果、函数按照值的方式进行返回。
C++98中的普通引用与const引用,在其引用实体上的区别:
(1)普通引用只能引用左值,不能引用右值;
(2)const引用既可以引用左值,也可以引用右值。
C++ 11中右值引用:
(1)只能引用右值;
(2)一般情况不能直接引用左值。
(3)右值引用引用左值方法:使用move函数进行转换。
如果一个类中涉及到资源的管理,用户必须显示的提供拷贝构造函数、赋值运算符重载、析构函数,否则编译器将会自动生成默认的成员函数。而当遇到拷贝对象或对象之间相互赋值的情况时,就会出现一些问题,比如:
class String {
public:
String(const char* str = "") {
if (nullptr == str) {
str = "";
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
String(const String& s)
: _str(new char[strlen(s._str) + 1])
{
strcpy(_str, s._str);
}
String& operator=(const String& s) {
if (this != &s) {
char* temp = new char[strlen(s._str) + 1];
strcpy(temp, s._str);
delete[] _str;
_str = temp;
}
return *this;
}
String operator+(const String& s) {
char* temp = new char[strlen(_str) + strlen(s._str) + 1];
strcpy(temp, _str);
strcpy(temp + strlen(_str), s._str);
String result(temp);
delete[] temp;
return result;
}
~String() {
if (_str) {
delete[] _str;
_str = nullptr;
}
}
public:
char* _str;
};
int main() {
String s1("hello ");
String s2("world");
String s3;
s3 = s1 + s2;
}
在执行s3 = s1 + s2时,其中operator+是以值形式返回。那么result在返回前,就必须创建一个临时对象,临时对象创建好后,result就被销毁了,然后使用返回的临时对象去赋值s3,赋值完成后,临时对象就被销毁掉。
可以发现,result、返回的临时对象、s3每个对象创建后,都有自己独立的空间,且它们存放的内容也相同,相当于创建了三个内容完全相同的对象,对于空间是一种浪费,程序的效率也较低。
优化思路:
如果可以将result的空间转移给临时对象,临时对象在赋值s3时,再将空间转移给s3,这样就相当于只开辟了一次空间。
实现方法:
可以发现,result相对于临时对象,临时对象相对于s3来说,都是将亡值,也就是右值。所以我们只需要实现一个移动构造函数和移动赋值函数即可,这样对于右值,则会自动调用移动函数,采用转移的方式进行构造或赋值。
注意:
(1)移动构造和移动赋值函数的参数不能设置为const类型的右值引用,否则资源无法转移导致移动语义失效。
(2)C++11之后,类中默认的成员函数就新添加了移动赋值、移动构造两个成员函数,同理若用户自己没有实现,编译器会默认生成,只不过默认生成的也是采用浅拷贝的方式实现的,所以当类中涉及到资源的管理时,也需要自己显示定义实现。
按照语法定义,右值引用只能引用右值,但有些情况下,也需要用右值去引用左值实现移动语义。
当需要用右值引用引用左值时,可以通过move函数将左值转化为右值。C++11中,std::move()函数,它并不是什么搬移功能,唯一的功能就是将一个左值强制转化为右值,然后实现移动语义。
应用场景示例:
比如以上代码,移动构造中,我们希望的是将s中的资源 全部转移到新对象中,但是s虽然是右值,但编译器并不认为s中的_name、_sno是右值,因为它们有自己的名字、可以对它们取地址,所以在初始化列表初始化时,并不会直接转移,而是会调用String中的拷贝构造函数。
所以此时就需要我们使用move函数,显示的告诉编译器将s中的_name、_sno当中右值处理,这样在初始化时就会选中去调用String的移动构造函数。
应用反例:
完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。
所谓完美:
函数模板在向其他函数传递自身参数时,如果相应实参是左值,它就应该被转发为左值;如果相应实参是右值,它就应该被转发为右值。
保留其他函数对转发来的参数的左右值属性,进行不同的处理。
比如:
函数func针对参数的左右值属性不同,有不同的处理方式,所以希望函数模板repost在转发参数时,保留其左右值属性,但通过以上写法是不能达到目的的,除法针对左右值属性,编写两个函数模板,但是这又降低了代码的复用性。
所以希望能够有一种方法,能够实现repost在转发参数时,保留其左右值属性,即完美转发。
(1)将模板函数(包括类模板和函数模板)的参数书写成“T&& 参数名”的形式,这样模板函数既可以接收左值,也可以接收右值引用。(注意:普通函数不行)
(2)C++11提供了模板函数std::forward