移动语义:
std::vector<String> v;
v.push_back(“string”);
1、调用 String::String(const char *);
2、调用 String::String(const String&);
3、调用 String::~String();
问题症结在于,临时对象的构造和析构带来了不必要的资源拷贝
如果有一种机制,可以在语法层面识别出临时对象,在使用临时对象构造新对象(拷贝构造)的时候,将临时对象所持有的资源『转移』到新的对象中,就能消除这种不必要的拷贝。
这种语法机制就是『右值引用』
左值、右值:
左值和右值都是针对表达式而言的,
左值是指表达式结束后依然存在的持久对象
右值是指表达式结束时就不再存在的临时对象
|
区分:
能对表达式进行取地址,则为左值
否则为右值
|
int a = 10;
int b = 20;
int * pflag = & a;
string s1 = "hello";
string s2 = "world";
const int & m = 1;
&a; //左值
&b; //左值
//&(a + b); //Error, 右值
//a = b; 左值
//&(a++); //Error, 右值
&(++a); //左值
//Complex c1;
//Complex c2 = c1++; 左值
&pflag; //左值
&(*pflag); //左值
|
//&string("hekk"); //Error,右值
//&(s1 + s2); //Error, 右值
int & ref1 = a; //左值引用绑定到左值
//int & ref2 = 1; //左值引用无法绑定到右值
const int & ref3 = 1; //常量左值引用可以绑定到右值
const int & ref4 = a; //常量左值引用也可以绑定到左值
cout << "ref3 = " << ref3 << endl;
cout << "ref4 = " << ref4 << endl;
//在c++11里面引用了右值引用 &&;
//int && ref5 = a;//右值引用无法绑定到左值
int && ref6 = 1;
|
左值引用:
1、根据其修饰符的不同,可分为非常量左值引用和常量左值引用
2、非常量左值引用只能绑定到非常量左值
如果允许绑定到常量左值和常量右值,则非常量左值引用可以用于修改常量左值和常量右值,
这明显违反了其常量的含义。
如果允许绑定到非常量右值,则会导致非常危险的情况出现,因为非常量右值是一个临时对象,
非常量左值引用可能会使用一个已经被销毁了的临时对象。
3、常量左值引用可以绑定到所有类型的值,包括 非常量左值、常量左值、右值(根据语法规则,无法区分出右值)所以引入右值引用&&
右值 vs 临时对象:
vector<int> get_all_scores()
{
vector<int> vec;
vec.push_back(1);
vec.push_back(2);
return vec;
}
vector<int> vec =get_all_scores();
这里实际上调用了三次构造函数
|
右值引用:
右值引用 int &&refa;
引入右值引用后,『引用』到『值』的绑定规则也得到扩充:
1、左值引用可以绑定到左值: int x; int &xr = x;
2、非常量左值引用不可以绑定到右值: int &r = 0;
3、常量左值引用可以绑定到左值和右值:int x; const int &cxr = x; const int &cr = 0;
4、右值引用可以绑定到右值:int &&r = 0;
5、右值引用不可以绑定到左值:int x; int &&xr = x;
6、常量右值引用没有现实意义(毕竟右值引用的初衷在于移动语义,而移动就意味着『修改』)。
移动语义--std::move:
编译器只对右值引用才能调用转移构造函数和转移赋值函数,而所有命名对象都只能是左值引用,如果已知一个命名对象不再被使用而想对它调用转移构造函数和转移赋值函数,也就是把一个左值引用当做右值引用来使用,怎么做呢?标准库提供了函数 std::move,这个函数以非常简单的方式将左值引用转换为右值引用。编译加上 -fno-elide-constructors
#include <iostream>
#include <string.h>
using namespace std;
class String
{
private:
char * _pstr;
public:
String();
String(const char * pstr);
String(const String & rhs);
String & operator = (const String & rhs);
//当同时定义了复制构造函数和移动构造函数,如果传递的实参是右值时,会优先调用移动构造函数;
//同理,移动赋值运算符函数也有这样的优先权
String(String && rhs);
String & operator = (String && rhs);
~String();
void swap(String & str);
friend ostream & operator << (ostream & os,const String & rhs);
};
String::String():_pstr(new char[1])
{
cout<< "String()" <<endl;
}
String::String(const char * pstr)
{
cout<<"String(const char *)"<<endl;
_pstr = new char[strlen(pstr)+1];
strcpy(_pstr,pstr);
}
//复制构造函数
String::String(const String & rhs):_pstr(new char[strlen(rhs._pstr)+1])
{
cout<<"String(const String &)"<<endl;
strcpy(_pstr,rhs._pstr);
}
//移动构造函数
String::String(String && rhs):_pstr(rhs._pstr) //先指向临时对象的空间
{
cout<<"String(String &&)"<<endl;
rhs._pstr = NULL; //在将临时对象的空间设为NULL
}
//移动赋值运算符 (形参的右值引用本身的一个左值)
String & String::operator = (String && rhs)
{
cout<<"String & operator = (String && rhs)"<<endl;
delete []_pstr;
_pstr = rhs._pstr;
rhs._pstr = NULL;
return *this;
}
|
String & String::operator = (const String & rhs)
{
cout<<"String & operator = (const String & rhs) "<<endl;
if(this!=&rhs)
{
String tmp(rhs); //先创建局部对象
swap(tmp); //再进行交换
}
return * this;
}
void String::swap(String & str)
{
char * ptmp = _pstr;
_pstr = str._pstr;
str._pstr = ptmp;
}
String::~String()
{
cout<<"~String()"<<endl;
delete []_pstr;
}
ostream & operator << (ostream & os,const String & rhs)
{
os<< rhs._pstr;
return os;
}
int main()
{
String s1="hello";
cout<<s1<<endl;
String s2="world";
cout<<s2<<endl;
String s3=String("hello"); //调用移动构造函数
cout<<"s3="<<s3<<endl;
s2=move(s1); //显示的将左值转换成右值,移动复制运算符
cout<<"s2="<<s2<<endl;
//使用move函数将某个对象s1转换成右值后,s1不能再使用
//cout<<"s1="<<s1<<endl; // 这里输出,会导致流错误,后面的都不能输出了
//cout<<cin.good()<<endl;
//cout<<"s3="<<s3<<endl;
return 0;
}
|
int func0() {}
String & func1()
{
}
|
//func2 与func0的效果类似
String && func2()
{
}
|