对象移动
右值的定义:只能出现在等号“=”右边的表达式。
当我们需要拷贝或者复制一个右值的时候,我们可以考虑移动右值。因为拷贝会新建一个对象,当对象初始化需要分配内存,拷贝的分配内存开销也很大,而移动不会新建对象,而是接管右值的资源。
对象移动是相对于对象拷贝的,在一些场景(比如:把对象作为一个参数传递)移动会比拷贝提升性能。还有一个原因是有些对象禁止拷贝:例如IO类和unique_ptr。
被移动的对象必须是右值,如果不是可以用std::move()显式转换为右值,但转换之后不能对这个对象的值做任何假设,分配内存的指针清空。
对移动操作的定义最好是noexcept
的,因为标准库容器比如vector在push_back
的过程中有可能会重新分配内存,需要将原来的对象拷贝或者移动到新内存。如果移动过程是有可能出现异常的话,那移动到一半出了异常,而前边移动的对象的值都改变了(移动之后不能对被移动的对象的值做任何假设),使得vector无法恢复成push_back
前的样子。所以移动构造函数如果没有noexcept
,vector会优先使用拷贝构造函数。
移动的两种具体形式:移动构造函数、移动赋值函数
class B{
public:
string s = "s";
B(){}
//移动构造函数
B(B&& b) noexcept :s(std::move(b.s)) {}
//移动赋值函数
B& operator=(B&& b)noexcept{
s = std::move(b.s);
}
};
如果类的对象都是可移动(存在移动构造函数)的而且这个类没有定义拷贝构造函数,operator= 和 析构函数,那编译器会为这个类合成移动构造函数。
class B{
public:
string s = "s";
};
int main()
{
B b;
B a = b;
cout << a.s<< b.s << endl;
return 0;
}
//输出为两个s
int main()
{
B b;
B a = std::move(b);
cout << a.s<< b.s << endl;
return 0;
}
//输出为一个s,b的s被移动给了a