右值引用的由来
看个例子:
#include <vector>
#include <iostream>
#include <string.h>
class String {
public:
String();
String(const char* str);
String(const String& rhs);
String& operator=(const String& rhs);
~String();
friend std::ostream& operator<<(std::ostream& os, const String& rhs);
private:
char* _pstr;
};
String::String()
:_pstr(new char[1]())
{
std::cout << "String::String()" << std::endl;
}
String::String(const char* str)
{
std::cout << "String::String(const char* str)" << std::endl;
_pstr = new char[strlen(str) + 1]();
strcpy(_pstr, str);
}
String::String(const String& rhs)
{
std::cout << "String::String(const String& rhs)" << std::endl;
_pstr = new char[strlen(rhs._pstr) + 1]();
strcpy(_pstr, rhs._pstr);
}
String& String::operator=(const String& rhs)
{
if (this == &rhs) {
delete[]_pstr;
_pstr = new char[strlen(rhs._pstr) + 1];
strcpy(_pstr, rhs._pstr);
}
return *this;
}
String::~String()
{
std::cout << "String::~String()" << std::endl;
if (_pstr != NULL) {
delete[]_pstr;
}
}
std::ostream& operator<<(std::ostream& os, const String& rhs)
{
os << rhs._pstr;
return os;
}
int main()
{
std::vector<String> vecInt;
vecInt.push_back("hello,world");
std::cout << vecInt[0] << std::endl;
}
执行结果如图
std::vector vecInt;
vecInt.push_back(“hello,world”);
可以看到这两句代码实际发生了这三个步骤:
①调用String::String(const char *); 通过hello,world构造一个临时对象
②String::String(const String&); 第一步是临时对象,放到容器中需要左值,因此要通过第一步构造的string对象调用复制构造函数构造出一个持久化的对象放到容器中
③String::~String()
这里产生了两次构造和析构。问题就在于临时对象的构造和析构带来了不必要的资源拷贝,有什么办法可以在第②步识别出临时对象,将临时对象的资源转移到新的对象中,消除不必要的拷贝,因此引入了右值引用。
const引用小知识点
const int& m = a; // 常量引用可以绑定到左值
const int& n = 123; // 常量引用也可以绑定到右值
int& p = a;
int& k = 123;// 编译报错,非常量引用的初始值必须为左值
因此上面的例子中String::String(const String&rhs) ,复制构造函数内部无法识别出rhs是左值还是右值,C++11引入右值引用,例:int& k = 123 改写成为 int&& k = 123
ps:常量右值引用没有实际意义,右值引用的初衷在于移动语义,而移动就意味着修改
具有移动语义的string
基于上面例子新增两个函数 移动复制构造函数和移动赋值运算符函数
#include <vector>
#include <iostream>
#include <string.h>
class String {
public:
String();
String(const char* str);
String(const String& rhs);
// 移动构造函数
String(String&& rhs);
String& operator=(const String& rhs);
// 移动赋值运算符函数
String& operator=(String&& rhs);
~String();
friend std::ostream& operator<<(std::ostream& os, const String& rhs);
private:
char* _pstr;
};
String::String()
:_pstr(new char[1]())
{
std::cout << "String::String()" << std::endl;
}
String::String(const char* str)
{
std::cout << "String::String(const char* str)" << std::endl;
_pstr = new char[strlen(str) + 1]();
strcpy(_pstr, str);
}
String::String(const String& rhs)
{
std::cout << "String::String(const String& rhs)" << std::endl;
_pstr = new char[strlen(rhs._pstr) + 1]();
strcpy(_pstr, rhs._pstr);
}
String& String::operator=(const String& rhs)
{
if (this == &rhs) {
delete[]_pstr;
_pstr = new char[strlen(rhs._pstr) + 1];
strcpy(_pstr, rhs._pstr);
}
return *this;
}
String::~String()
{
std::cout << "String::~String()" << std::endl;
if (_pstr != NULL) {
delete[]_pstr;
}
}
String::String(String&& rhs)
{
std::cout << "String::String(String&& rhs)" << std::endl;
_pstr = rhs._pstr;
rhs._pstr = NULL; // 注意这里不要忘了
}
String& String::operator=(String&& rhs)
{
std::cout << "String& String::operator=(String&& rhs)" << std::endl;
if (this == &rhs) {
delete []_pstr;
_pstr = rhs._pstr;
rhs._pstr = NULL; // 注意这里不要忘了
}
return *this;
}
std::ostream& operator<<(std::ostream& os, const String& rhs)
{
os << rhs._pstr;
return os;
}
int main()
{
std::vector<String> vecInt;
vecInt.push_back("hello,world");
std::cout << vecInt[0] << std::endl;
}
执行结果如下,可以看到helloworld产生的临时对象申请的空间直接转移给了新对象:
注意:一定不要忘了给临时对象赋空,不然临时对象销毁时就把该空间释放了。
参考资料:
https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers
http://thbecker.net/articles/rvalue_references/section_01.html
https://www.jianshu.com/p/d19fc8447eaa
https://zhuanlan.zhihu.com/p/107445960
https://zhuanlan.zhihu.com/p/99524127
《深入理解C++11》