C++的平凡类型与std::move
最近写CMU15-445的时候,project 1里面有个任务类似于写unique_ptr
智能指针PageGuard
,创建时增加引用计数,销毁时减少引用计数。然后这里有个让人疑惑的地方,就是他要求写移动构造函数。当时我想着,这跟用= default
应该没啥区别,后来不放心,还是做了个实验验证下,发现和想象中的还是有些出入。
废话不多说,直接贴代码:
#include <bits/stdc++.h>
using namespace std;
struct A {
int val = 0;
string msg = "";
};
int main()
{
A a1{114514, "1919810"};
printf("a1{%d,%s}\n", a1.val, a1.msg.c_str());
A a2(std::move(a1));
printf("a2{%d,%s}\n", a2.val, a2.msg.c_str());
printf("a1{%d,%s}\n", a1.val, a1.msg.c_str());
return 0;
}
然后结果是,a2
能成功继承a1
的所有数据,但是在move后的a1
中,只有std::string
的值被清空,而int
类型的数据仍然保持了原有值,而非初始化到0。这个结果就与成员变量的平凡性有关了:对于平凡类型,move和直接复制该类型没有任何区别。也就是说,仅仅对于非平凡类型而言,才会存在真实的move动作,并将被move对象重置为设定的某个中间状态——平凡类型要求使用编译器隐式生成的移动构造函数,因而并不会对原有数据进行修改,move之后还是保持原有的值。
由于规定以C语言生成的所有数据结构都是平凡的,我们可以推测指针本身也是一种平凡类型,move后不会被清空。那么这下问题就解决了:要求我们自己实现移动构造函数的原因是,在对象中使用了C指针而非智能指针,并不能通过move的方式来解除被move对象与内存区域的绑定,需要自己手动使用nullptr
来重置这些指针,防止它们在预期的生命周期外修改数据,制造未定义行为。