右值引用
右值一般指的是无法用&取到存储地址的。比如常量10
那么我们可以使用&&来获得右值
int &&j=10;
int e=10;
int &&j=e;//错误,因为e是左值
移动构造函数
#include <iostream>
using namespace std;
class demo
{
public:
demo() :num(new int(0))
{
cout << "construct!" << endl;
}
//拷贝构造函数
demo(const demo &d) :num(new int(*d.num))
{
cout << "copy construct!" << endl;
}
~demo()
{
if(num != nullptr)
{
delete num;
num = nullptr;
}
cout << "class destruct!" << endl;
}
private:
int *num;
};
demo get_demo()
{
return demo();
}
int main()
{
demo a = get_demo();
return 0;
}
在这个过程中,如果没有经过返回值优化的话,会在get_demo函数return时返回一个临时变量,该临时变量会调用拷贝构造函数从return后面创建的demo对象中得到值,同时demo对象本身销毁;然后demo会再次调用拷贝构造函数从临时对象处赋值,同时也销毁临时对象。这样其实一共调用了一次构造函数(return demo()),两次拷贝构造函数(函数内创建的demo对象赋值给临时变量,临时变量赋值给demo),两次析构函数(临时变量和函数内创建的A对象),每次构造和析构的过程都要new和delete资源,这对于计算来说是极大的浪费。
过程中生成的都是匿名对象,无法通过&获取地址,因此它是一个右值,可以通过引入移动构造函数来优化程序。
可以看到两次深拷贝的过程都是通过移动构造函数来完成的,在移动构造函数中,参数是一个demo的右值引用,并且直接将新生成的对象的指针成员指向匿名对象所申请的堆空间,将匿名对象的成员指针置空,这样就防止每次调用拷贝构造函数向堆上申请新的内存空间了,大大提高了效率。
所谓移动语义,指的就是以移动而非深拷贝的方式初始化含有指针成员的类对象。简单的理解,移动语义指的就是将其他对象(通常是临时对象)拥有的内存资源“移为已用”。
move
默认情况下,左值初始化同类对象只能通过拷贝构造函数完成,如果想调用移动构造函数,则必须使用右值进行初始化。C++11 标准中为了满足用户使用左值初始化同类对象时也通过移动构造函数完成的需求,新引入了 std::move() 函数,它可以将左值强制转换成对应的右值,由此便可以使用移动构造函数。
#include <iostream>
using namespace std;
class MoveDemo
{
public:
MoveDemo() :num(new int(0))
{
cout << "construct!" << endl;
}
MoveDemo(const MoveDemo &d) :num(new int(*d.num))
{
cout << "copy construct!" << endl;
}
//添加移动构造函数
MoveDemo(MoveDemo &&d) :num(d.num)
{
d.num = nullptr;
cout << "move construct!" << endl;
}
~MoveDemo()
{
if (num != nullptr)
{
delete num;
num = nullptr;
}
cout << "class destruct!" << endl;
}
public:
int *num;
};
MoveDemo get_demo()
{
return MoveDemo();
}
int main()
{
MoveDemo demo;
cout << "demo2:\n";
MoveDemo demo2 = demo;
//cout << *(demo2.num) << endl; //可以执行
cout << "demo3:\n";
MoveDemo demo3 = std::move(demo);
//此时 demo.num = NULL,因此下面代码会报运行时错误
//cout << *(demo.num) << endl;
return 0;
}
参考
https://blog.csdn.net/bureau123/article/details/112696446
https://www.cnblogs.com/wickedpriest/p/12662746.html