c++的引用有左值引用和右值引用c++11提出了右值引用
通俗的将左值引用–给左值取别名
右值引用就是给右值取别名
如何理解右值引用
理解右值引用首先要明白什么是左值什么是右值
eg:
int a = 10;//a左值10右值
int b = a;//b左值,a不是右值
得出结论:
赋值号左边的是左值(可以被修改的值)
,赋值号右边的不一定是右值,
右值:(1)常量/表达式的返回值(临时变量)(1+3)–纯右值
(2)将亡值:声明周期将要接收的对象,eg:在一个类里在值返回时的临时对象
(2.1)问题:(1)左值引用能否引用右值–int& left_ref1 = 10;—不行,==类似于权限的放大10是常量不能修改但是有了左边引用后会修改所以不行—解决方法–加const
cont int& left_ref1 = 10;—得出结论左值引用不能引用右值,但是const左值引用ok,const引用既能引用左值又能引用右值;
问题(2)右值引用能否引用左值?
int&& right_ref2 = a;----不行
需要变成这样就可以了int&& right_ref2 = move(a);
结论右值引用要将其变成将亡对象才能用
右值引用的书写格式:
类型&&引用变量名字=实体;
右值引用的主要用途
右值引用最长常见的一个使用地方就是:与移动语义结合,减少无必要资源的开辟来提高代码的运行效率。
-
++ = – []返回this出了作用域参数还在用引用返回
如果是在类里,尤其是涉及到深拷贝的问题,用+±-[]代价很大
++
eg:使用+
class String
{
public:
String(char str = “”)
{
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
// 拷贝构造 + 赋值
String(const String& s)
: _str(new char[strlen(s._str) + 1])
{
cout << “String(const String& s)” << endl;
strcpy(_str, s._str);
}
String& operator=(const String& s)
{
cout << “String& operator=(const String& s)” << endl;if (this != &s) { char* pTemp = new char[strlen(s._str) + 1]; strcpy(pTemp, s._str); delete[] _str; _str = pTemp; } return *this;
}
~String()
{
if (_str)
delete[] _str;
}// s1 += s2;
String& operator+= (const String& s)
{
//this->Append(s._str);
return *this;
}// s1 + s2
String operator+ (const String& s)
{
String ret(*this);
//ret.Append(s._str);
return ret;
}
private:
char* _str;
};
String header = “http://”;
String domain = “www.cplusplus.com”;
String url_array = header;
url_array += domain;
cout << “------------------” << endl;
String url_vector;
url_vector = header + domain;
return 0;}
string 类型为深拷贝,+会发现调用了
两次拷贝构造和一次赋值对内存消耗过大
如何解决—用移动构造和移动赋值
class String
{
public:
String(char* str = “”)
{
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
// C++11 右值 纯右值 + 将亡值
// 移动构造 + 移动赋值
//String s1(tmp) -> tmp将亡值
String(String&& s)
{
cout << “String(String&& s)” << endl;
_str = s._str;
s._str = nullptr;
}
// s1 = tmp -> tmp将亡值
String& operator=(String&& s)
{
cout << “String(String&& s)” << endl;
swap(_str, s._str);
return this;
}
// 拷贝构造 + 赋值
String(const String& s)
: _str(new char[strlen(s._str) + 1])
{
cout << “String(const String& s)” << endl;
strcpy(_str, s._str);
}
String& operator=(const String& s)
{
cout << “String& operator=(const String& s)” << endl;
if (this != &s)
{
char pTemp = new char[strlen(s._str) + 1];
strcpy(pTemp, s._str);
delete[] _str;
_str = pTemp;
}
return *this;
}
~String()
{
if (_str)
delete[] _str;
}
// s1 += s2;
String& operator+= (const String& s)
{
//this->Append(s._str);
return *this;
}
// s1 + s2
String operator+ (const String& s)
{
String ret(this);
//ret.Append(s._str);
return ret;
}
private:
char _str;
};
int main()
{
String header = “http://”;
String domain = “www.cplusplus.com”;
String url_array = header;
url_array += domain;
cout << “------------------” << endl;
String url_vector;
url_vector = header + domain;
结果如下
会发现只调用了一次拷贝构造然后调用了两次移动构造
移动构造移动赋值–接收右值
拷贝构造拷贝赋值–接收左值引用
c++右值 纯右值 将亡值:出了作用域临时对象就销毁了
自定义类型 ret(拷贝构造临时对象)
将亡值把你的值给我然后我自己的不用了置空(类似于交换swap)
右值引用没有提高效率,左值引用提高效率(减少拷贝)
右值引用间接提高效率–函数返回值,传值返回产生临时对象,在去掉拷贝构造,接收到的都是将忙值
string&&r_ref=mov(str);
string copy=move(str);//意思就是空间直接转移到了copy,r_ref空间没有了
std::move()函数位于 头文件中,这个函数名字具有迷惑性,它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,通过右值引用使用该值,实现移动语义。 注意:被转化的左值,其声明周期并没有随着左右值的转化而改变,即std::move转化的左值变量lvalue不会被销毁。
左值引用与右值引用的区别
右值引用–(1)与左值引用对比,主要引用右值,给右值取别名,
区别:概念(1)取别名,左值引用可以给const取别名,用:右值引用弥补减少左值的不足,减少拷贝
左值减少了传参过程的拷贝,部分传值,出了作用域对象在,里面改变外面看的见,(operator[]返回值,赋值可以修改value,出了作用域对象不在不能用引用返回,)而vector ,map,会增加深拷贝,右值引用解决传值问题涉及深拷贝的问题
(1)左值引用解决了传参返回值,但是返回对象出了作用域不再不能用引用,右值就是补这个坑,提供移动构造和移动赋值,构成重载,
(2)左值引用:1引用传参2引用传返回值
缺陷:传值返回没办法解决
右值引用:1增加了移动构造和移动赋值跟拷贝构造和拷贝赋值构成重载,几乎没有消耗
2传值返回拷贝时就会自动调用移动构造和移动赋值,提高效率operator+ vector<>返回值
3类似于容器的插入数据接口,提供右值引用的接口可以减少对象拷贝
右值引用当为传值返回只是交换将亡对象的值类似于延长了生命周期但是还不能认为是延长只是交换以后的那个对象用了所以叫类似延长,而对于将亡对象本身生命周期是没有延长的
右值引用的弊端
如果将移动构造函数声明为常右值引用或者返回右值的函数声明为常量,都会导致移动语义无法实现。
解决方法
完美转发(forward)是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。
完美转发是目标函数总希望将参数按照传递给转发函数的实际类型转给目标函数,而不产生额外的开销,所谓完美:函数模板在向其他函数传递自身形参时,如果相应实参是左值,它就应该被转发为左值;如果相应实参是右值,它就应该被转发为右值。这样做是为了保留在其他函数针对转发而来的参数的左右值属性进行不同处理(比如参数为左值时实施拷贝语义;参数为右值时实施移动语义)。
void f1(String && s)
{
//如果没有完美转发传参接受参数后里面的属性都会认为是左值,
String copy(String(s));
}
void f1(String && s)
{
String copy(std::forward(s));
}
int main()
{
String str(“hello”);
f1(move(str));
String copy(move(str));
}