C++primer移动构造函数笔记
c++支持移动而非拷贝一个对象的能力。在很多情况下我们都会拷贝一个对象,对象在拷贝后就立即销毁了。这种情况下使用移动会大幅度提升性能。
什么是移动
移动一个对象就是直接将它使用的内存交由另一个对象使用。类似于用一个指针指向另一个指针指向的对象,不同的是移动后的对象只支持赋新值和销毁操作,不能再依赖于之前的值。
右值引用
了解移动之前我们必须了解什么是右值引用(&&),右值引用就是必须绑定到右值的引用。右值引用有一个重要的性质–只能绑定到一个将要销毁的对象。右值引用有和左值引用相反的绑定特性,右值引用可以绑定到要求转换的表达式字面常量或是返回右值的表达式。
int i=0;
int &&r1=i; //错误,不能绑定到左值
int &&r2=100;//正确,可以绑定到字面常量
int &&r3=i*42;//正确,可以绑定到返回右值的表达式
因为右值引用只能绑定到临时对象,我们可以从绑定到右值引用的对象窃取“状态”。使用右值引用的代码可以自由接管所引用对象的资源。
move函数介绍
虽然我们不能将右值直接绑定到一个左值上,但我们可以使用标准库函数move来将一个左值显式的转换成一个右值引用类型。
int &&r4=std::move(i);
调用move代表告诉编译器,我们希望像一个右值一样使用它。所以在对象调用move函数后,我们不能在使用它的值,只能对它赋予一个新的值,或者销毁它。
移动构造函数与移动赋值函数
其他的标准库类和内置类型都支持移动操作,若我们自己定义的类也支持移动操作,将大幅度提升性能。
下面是一个简单的使用移动构造函数的类,不要纠结于它效率高不高,我们只是用它学习一下。
class example{
public:
void print(){
cout<<*a;}
example(const example &q):a(new int(*(q.a))){
} //拷贝构造函数
example(int m):a(new int(m)){
} //构造函数
example(example &&p)noexcept:a(p.a){
p.a=0;} //移动构造函数
example &operator=(example &&p) noexcept //移动赋值描述符
{
if (this!=&p){
free();
a=p.a;
p.a=nullptr;
}
return *this;
}
~example(){
if (a) delete a;};
private:
void free(){
if (a) delete a;}
int *a=0;
};
我们可以看到移动构造函数是如下形式
example(example &&p)noexcept:a(p.a){
}
与拷贝构造函数不同,移动构造函数不分配任何新内存,它接管给定的example中的内存。在接管内存之后,它将给定对象中的指针置为nullptr.这样就完成给定对象的移动操作。最终,移后源对象会被销毁,意味着将其上运行析构函数。example的析构函数将释放a的内存。若我们忘记了将源对象的a置为nullptr,刚移动的内存就会被释放。
所以我们应该保证移后源对象是可析构的。
移动赋值描述符
example &operator=(example &&p) noexcept //移动赋值描述符
{
if (this!=&p){
free();
a=p.a;
p.a=nullptr;
}
return *this;
}
移动赋值描述符执行与析构函数和移动构造函数相同的工作。
需要注意的是我们必须要注意自赋值情况,我们不能在使用右侧运算对象的资源之前就释放掉左侧运算对象的资源(可能是相同的资源).