最近看了侯捷老师的书籍,看到了右值引用,梳理一下右值引用的用法,以及特点:
右值、左值
既然是右值引用,我们首先要理解的是什么是右值什么是左值:
1、位于赋值号(=)左侧的表达式就是左值;反之,只能位于赋值号右侧的表达式就是右值;
2、有名称的、可以获取到存储地址的表达式即为左值;反之则是右值。
左值在内存中有特定地址的量,在程序中存在的时间比较长;右值实在寄存器中的量,在程序中存在的时间比较短暂;简单的说我们区分一个左值和右值的办法就是看能不能对表达式取地址,如果能,则为左值,否则为右值。
int a = 1;
1 = a; //错误,1 不能为左值
int b = 1; // b 是一个左值
a = b; // a、b 都是左值,只不过将 b 可以当做右值使用
右值引用
所谓右值引用就是必须绑定到右值的引用,我们通过&&来获得右值引用。根据上边右值的性质我们可以得到右值引用又一个重要的性质只能绑定到一个将要销毁的对象,因此我们可以自由地将一个右值引用的资源“偷”到另一个对象中。一般而言,一个左值表达式表示的是一个对象的身份,而一个右值表达式表示的是对象的值。
类似于任何引用,右值引用也不过是某个对象值的另一个名字。
左值引用只能绑定左值,右值引用只能绑定右值,如果绑定的不对,编译就会失败。但是,常量左值引用却是个奇葩,它可以算是一个**“万能”的引用类型**,它可以绑定非常量左值、常量左值、右值,而且在绑定右值的时候,常量左值引用还可以像右值引用一样将右值的生命期延长。
int i = 42;
int &r = i;//正确,r引用i
int &&rr = i;//错误,右值引用不能绑定到左值
int &r2 = i *2 ;//错误,i*2是一个右值
const int &r3 = i*2;//正确,可以将一个const的引用绑定到右值上
int &&r2 = i * 2;//正确,将r2绑定到右值
int &&R3=r2;//错误,表达式r2是一个右值,
返回左值引用的函数,连同赋值、下标、解引用和前置递增、递减都是返回左值的表达式的例子,我们可以把一个左值引用绑定到这类表达式的结果上;
返回非引用的函数,连同算术、关系位以及后置递减、递增运算都生成右值。
由于右值引用只能绑定到临时对象,我们可以得到:
1、所引用的对象将要被销毁;
2、该对象没有其他的用户。
使用右值引用的代码可以自由的接管所引用的对象的资源。
其实
来看一个例子:
#include <iostream>
using namespace std;
class Test{
public:
Test(){cout << "默认构造方法"<<endl;}
Test(const Test &o) {
cout << "拷贝构造参数" << endl;
}
};
Test ReturnRvalue() {
return Test(); //返回一个临时对象
}
void AcceptVal(Test a) {
}
void AcceptRef(const Test& a) {
}
int main() {
cout << "pass by value: " << endl;
AcceptVal(ReturnRvalue()); // 应该调用两次拷贝构造函数
cout << "pass by reference: " << endl;
AcceptRef(ReturnRvalue()); //应该只调用一次拷贝构造函数
}
这里的输出如下:
并没有和预计的输出一样,这里编译器开启了返回值优化。就是把临时对象作为右值引用。关于其中返回值对象问题可以看一下我的另一篇文章返回值是对象问题
移动构造和移动赋值
类似于string类,如果我们自己的类也同时支持移动和拷贝,那么也能从中收益。为了让我们自己的类型支持移动操作,需要为其定义移动构造函数和移动赋值函数,这两个成员类似对应的操作,但是他们从给定对象“窃取资源”而不是拷贝资源。
类似拷贝构造函数,移动构造函数的第一个参数是该类类型的一个引用,不同于拷贝构造函数的是,这个引用参数在移动构造函数中是一个右值引用。于拷贝构造函数一样,任何额外的参数都必须有默认的实参。
除了完成资源的移动,移动构造函数还必须确保的是移动后的对象的所有权归属于新创建的这个对象。看一下例子我们实现一个My_string类这个类中有移动构造函数和移动赋值函数同时还实现了一个My_stringNo类这个类没有移动构造函数和移动赋值函数,我们可以看一下差别:
#include <iostream>
#include <cstring>
#include <vector>
#include<time.h>
using namespace std;
class My_string{
public:
static size_t Dctor;// 呼叫默认构造函数的次数
static size_t Ctor;// 呼叫构造函数的次数
static size_t CCtor;// 呼叫拷贝构造函数的次数
static size_t CAsgn;// 呼叫拷贝赋值函数的次数
static size_t Mctor;//呼叫移动构造函数的次数
static size_t MAsgn;//呼叫移动赋值函数的次数
static size_t Dtor ;//呼叫析构函数的次数
private:
char* _data;
size_t _len;
public:
//默认构造函数
My_string():_data(nullptr),_len(0){
Dctor++;
}
//构造函数
My_string(const char *p):_len(strlen(p)){
Ctor++;
_data = new char[_len+1];
memcpy(_data,p,_len);
_data[_len] = '\0';
}
//拷贝构造函数
My_string(const My_string & str):_len(str._len){
CCtor++;
_data = new char[_len+1];
memcpy(_data,str._data,_len);
_data[_len] = '\0';
}
//移动构造函数
My_string( My_string && str) noexcept:_len(str._len),_data(str._data){
Mctor++;
str._len=0;
str._data=nullptr;
}
//拷贝赋值
My_string & operator=(const My_string &str){
CAsgn++;
if(this != &str){
if(_data) delete _data;
_len = str._len;
_data = new char[_len+1];
memcpy(_data,str._data,_len);
_data[_len] = '\0';
}
return *this;
}
// 移动赋值
My_string & operator=( My_string&&str) noexcept{
MAsgn++;
if(this != &str){
if(_data) delete _data;
_data = str._data;//移动操作
_len = str._len;
str._len=0;
str._data=NULL; //放弃管理权
}
return *this;
}
//dtor
virtual ~My_string(){
Dtor++;
if(_data){
delete _data;
}
}
};
class My_stringNo{
public:
static size_t Dctor;// 呼叫默认构造函数的次数
static size_t Ctor;// 呼叫构造函数的次数
static size_t CCtor;// 呼叫拷贝构造函数的次数
static size_t CAsgn;// 呼叫拷贝赋值函数的次数
static size_t Dtor ;//呼叫析构函数的次数
private:
char* _data;
size_t _len;
public:
//默认构造函数
My_stringNo():_data(nullptr),_len(0){
Dctor++;
}
//构造函数
My_stringNo(const char *p):_len(strlen(p)){
Ctor++;
_data = new char[_len+1];
memcpy(_data,p,_len);
_data[_len] = '\0';
}
//拷贝构造函数
My_stringNo(const My_stringNo & str):_len(str._len){
CCtor++;
_data = new char[_len+1];
memcpy(_data,str._data,_len);
_data[_len] = '\0';
}
//拷贝赋值
My_stringNo & operator=(const My_stringNo &str){
CAsgn++;
if(this != &str){
if(_data) delete _data;
_len = str._len;
_data = new char[_len+1];
memcpy(_data,str._data,_len);
_data[_len] = '\0';
}
return *this;
}
//dtor
virtual ~My_stringNo(){
Dtor++;
if(_data){
delete _data;
}
}
};
size_t My_string::CAsgn=0;
size_t My_string::CCtor=0;
size_t My_string::Ctor=0;
size_t My_string::Dctor=0;
size_t My_string::MAsgn=0;
size_t My_string::Mctor=0;
size_t My_string::Dtor=0;
size_t My_stringNo::CAsgn=0;
size_t My_stringNo::CCtor=0;
size_t My_stringNo::Ctor=0;
size_t My_stringNo::Dctor=0;
size_t My_stringNo::Dtor=0;
int main()
{
vector<My_string> vecStr;
vecStr.reserve(100000); //先分配好1000个空间,不这么做,调用的次数可能远大于1000
clock_t timeStart = clock();
for(int i=0;i<100000;i++){
vecStr.push_back(My_string("hello"));
}
cout <<"默认构造函数的次数"<< My_string::Dctor << endl;
cout <<"构造函数的次数"<< My_string::Ctor << endl;
cout <<"拷贝赋值函数的次数"<< My_string::CAsgn << endl;
cout <<"拷贝构造函数的次数"<<My_string::CCtor << endl;
cout <<"移动赋值次数" <<My_string::MAsgn << endl;
cout << "移动构造次数"<<My_string::Mctor << endl;
cout <<"析构函数的次数 "<< My_string::Dtor << endl;
cout <<"时间" <<(clock()-timeStart) << endl;
cout << "------------------------"<<endl;
vector<My_stringNo> vecStr2;
vecStr2.reserve(100000); //先分配好1000个空间,不这么做,调用的次数可能远大于1000
clock_t timeStart2 = clock();
for(int i=0;i<100000;i++){
vecStr2.push_back(My_stringNo("hello"));
}
cout <<"默认构造函数的次数"<< My_stringNo::Dctor << endl;
cout <<"构造函数的次数"<< My_stringNo::Ctor << endl;
cout <<"拷贝赋值函数的次数"<< My_stringNo::CAsgn << endl;
cout <<"拷贝构造函数的次数"<<My_stringNo::CCtor << endl;
cout <<"析构函数的次数 "<< My_stringNo::Dtor << endl;
cout <<"时间" <<(clock()-timeStart2) << endl;
cout << "------------------------"<<endl;
return 0;
}
运行结果:
我们可以发现,没有移动构造函数,在Vector中花费的时间更长一些,因为在没有移动构造函数的情况的我们需要一个一个字符的复制到vector中,但是有移动得我们可以直接把那个对象偷到vector中,不过我们需要源对像放弃管理权。其实就是下面这个操作