第二章的内容:主讲右值引用
1、左值和右值区分的判据是看是否可对表达式用&取址,左值可以,右值不可以
//1、左值引用相当于给变量起个别名,故必须是变量,而不能是常量
int aa = 1;
int &b = aa;//ok
int &k = 2;//error 必须为左值
//2、右值引用
int&& a = 1; //ok,实质上就是给不具名(匿名)变量起个别名
int b = 1;
int && c = b; //error! 不能将一个左值赋值给一个右值引用
2、移动构造和移动赋值
传统的左值操作会开销很大的内存,比如在深拷贝构造函数中,对某变量进行拷贝赋值时,需重新申请一块内存对其进行赋值
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
class MyString
{
public:
static size_t CCtor; //统计调用拷贝构造函数的次数
static size_t MCtor; //统计调用移动构造函数的次数
static size_t CAsgn; //统计调用拷贝赋值函数的次数
static size_t MAsgn; //统计调用移动赋值函数的次数
public:
// 构造函数
MyString(const char* cstr = 0)
{
if (cstr) {
m_data = new char[strlen(cstr) + 1];
strcpy(m_data, cstr);
}
else {
m_data = new char[1];
*m_data = '\0';
}
}
// 拷贝构造函数
MyString(const MyString& str)
{
//MyString("hello")只是临时对象,拷贝完就没什么用了
//下面的new是重新申请一块内存来存放它
//造成了没有意义的资源申请和释放操作
CCtor++;
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
cout << "拷贝" << endl;
}
// 移动构造函数
MyString(MyString&& str)
:m_data(str.m_data)
{
MCtor++;
str.m_data = nullptr; //不再指向之前的资源了
cout << "移动拷贝" << endl;
}
// 拷贝赋值函数 =号重载
MyString& operator=(const MyString& str)
{
CAsgn++;
if (this == &str) // 避免自我赋值!!
return *this;
delete[] m_data;
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
return *this;
}
// 移动赋值函数 =号重载
MyString& operator=(MyString&& str)
{
MAsgn++;
if (this == &str) // 避免自我赋值!!
return *this;
delete[] m_data;
m_data = str.m_data;
str.m_data = nullptr; //不再指向之前的资源了
return *this;
}
~MyString()
{
delete[] m_data;
}
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
size_t MyString::CCtor = 0;
size_t MyString::MCtor = 0;
size_t MyString::CAsgn = 0;
size_t MyString::MAsgn = 0;
int main()
{
vector<MyString> vecStr;
vecStr.reserve(10); //先分配好10个空间
for (int i = 0; i<10; i++)
{
//移动构造函数与拷贝构造函数的区别是,拷贝构造的参数是const MyString& str,是常量左值引用,
//而移动构造的参数是MyString&& str,是右值引用,而MyString("hello")是个临时对象,是个右值,
//优先进入移动构造函数而不是拷贝构造函数。
vecStr.push_back(MyString("hello"));
}
cout << "CCtor = " << MyString::CCtor << endl;
cout << "MCtor = " << MyString::MCtor << endl;
cout << "CAsgn = " << MyString::CAsgn << endl;
cout << "MAsgn = " << MyString::MAsgn << endl;
}
移动构造函数与拷贝构造不同,它并不是重新分配一块新的空间,将要拷贝的对象复制过来,而是"偷"了过来,将自己的指针指向别人的资源,这就节省了内存开销,然后将别人的指针修改为nullptr
,这一步很重要,如果不将别人的指针修改为空,那么临时对象析构的时候就会释放掉这个资源,"偷"也白偷了。下面这张图可以解释copy和move的区别:
2、move的作用是将一个左值强制转换为一个右值引用,我们可以通过右值引用来使用该值,以用于移动构造函数,消减内存开销
MyString str1("hello"); //调用构造函数
MyString str2(str1); //调用拷贝构造函数
MyString str3(std::move(str1)); // 调用移动构造函数
3、emplace_back取代push_back
emplace_back没有内存的移动,可以节省中间变量的内存开销,建议多使用emplace_back
struct A
{
int x;
double y;
//A(int a, double b):x(a), y(b)
//{}
};
int main()
{
vector<A> vec;
A a{ 1, 2 };
vec.emplace_back(a);
//vec.push_back(a);
vec.emplace(vec.begin(), a);
cout << vec.size() << endl;
cout << vec.at(1).y << endl;
}