1、什么是移动构造函数
我们知道拷贝构造函数分为浅拷贝和深拷贝。
(1)浅拷贝:当类含有指针变量时,浅拷贝会发生错误。
(2)深拷贝:每次都要全部赋值一份,内存消耗比较大。
移动构造函数就是为了解决拷贝构造函数存在的问题。移动构造函数将对象的状态或者所有权从一个对象转移到另一个对象,没有内存的搬迁或者内存拷贝,所以可以提高效率。
移动构造函数跟拷贝构造函数的几点不同:
(1)移动构造函数没有给成员变量新申请内存,而是直接将原来对象的值移动到新对象。
(2)移动后原对象的值都归属于新对象,原对象的指针设为NULL;因为移动后原对象会被析构,而NULL可以被多次free,所以这样做比较安全。
(3)移动构造函数的形参不能是const,因为移动后要修改原对象的成员变量指向NULL。
(4)移动构造函数多了一步判断:判断当前指针与输入的右值地址是否相同。
这是为了防止把自身作为输入进行移动赋值,这样会导致得到的对象成员指向NULL。
例子:
(1)移动构造函数:
class A {
public:
A() {
num = (int*)malloc(sizeof(int));
*num = 2;
}
A(const A& x) {
this->num = new int(*x.num);
cout << "拷贝构造函数" << endl;
}
A(A&& x) {
if (this != &x) {
this->num = x.num;
x.num = nullptr;
cout << "移动构造函数" << endl;
}
}
~A() {
free(num);
}
int* num;
};
int main()
{
A a;
A b = a; //拷贝构造
A c(move(a)); //移动构造, move()函数把a转换成右值
if (a.num == nullptr) { //对象a的num已经为nullptr
std::cout << "a.num is null" << std::endl;
}
return 0;
}
(2)移动赋值运算符:
class A {
public:
A() {
num = (int*)malloc(sizeof(int));
*num = 2;
}
void operator = (const A& x) {
this->num = new int(*x.num);
cout << "拷贝赋值函数" << endl;
}
void operator = (A&& x) {
if (this != &x) {
this->num = x.num;
x.num = nullptr;
cout << "移动赋值函数" << endl;
}
}
~A() {
free(num);
}
int* num;
};
int main()
{
A a;
A b; //注意跟拷贝构造的不同,拷贝赋值时b对象先生成,后再赋值
b = a; //拷贝赋值
A c;
c = move(a); //移动赋值
return 0;
}
2、移动构造函数和移动赋值运算符的规则:
关于移动构造函数和移动赋值运算符,有下面几条规则:
(1)如果一个类定义了自己的拷贝构造函数、拷贝赋值运算符或者析构函数,则编译器就不会生成默认移动构造函数和移动赋值运算符。因为只要类自己有复制对象或者释放对象的操作,编译器就不会帮忙合成移动动作的相关函数,这样可以防止编译器生成不是程序员想要的移动构造函数或移动赋值运算符。
(2)只有1个类没有定义自己的拷贝构造函数、拷贝赋值运算符、析构函数,且类的每个非静态成员都可以被move时,编译器才会为该类合成移动构造函数或移动赋值运算符。
什么叫成员可被move呢?
(1)基础数据类型。如int、byte等。
(2)如果成员变量是一个类,但该类有移动相关的函数。
为了加深对规则的理解,我们来看一个例子:
class MyDemo {
public:
int m_i;
string s;
//~MyDemo(){}
};
int main()
{
MyDemo demoA;
demoA.m_i = 2;
demoA.s = "Hello World";
cout << "\n before move, demoA.s = " << demoA.s << " , demoA.m_i=" << demoA.m_i << endl;
MyDemo demoB = std::move(demoA);
cout << " after move, demoA.s = " << demoA.s << " , demoA.m_i=" << demoA.m_i << endl;
return 0;
}
代码的执行结果如下:
为什么demoA.s的值在move后,变成空了呢?难道被move走了?
其实这是执行了string类移动构造函数的结果。
如果往类MyDemo添加一个析构函数,再看执行结果:
发现这时demoA.s的值保持不变。
这是因为加了析构函数后,编译器就不会为类MyDemo生成默认移动构造函数了。