以前就知道C++里的结构体不能随便拷贝,可能有性能问题,也可能导致double free的问题。C++11里提出move语义,并不惊讶,也并没有留意,最近查一个Bug时,发现自己对move语义的了解不周全,具体例子记录于此。
move的好处是能够减少构造和析构次数,同时能方便的管理内存,便于遵从RAII原则。举个例子,按以前C++写法:
struct BackNode
{
char *data;
uint32_t offset;
uint32_t length;
};
int main()
{
BackNode one;
vector<BackNode> vb;
for (int i = 0; i < 10; ++i) {
one.data = new char[1024];
one.offset = 0;
one.length = 0;
vb.push_back(one);
}
//use vb
//.....
//release
for (int i = 0; i < 10; ++i) {
delete[] vb[i].data;
vb[i].data = NULL;
}
return 0;
}
结构体BackNone很清晰,但是对它的操作很麻烦,内存得记得去释放,push_back的时候会有结构体的多次构造。如果打算将内存的分配和回收放到BackNode的构造和析构函数里,这个时候得实现BackNode(const BackNode& bn)函数,同时在该函数里分配新内存,拷贝data的内容。所以你得这么写:
struct BackNode
{
BackNode(uint32_t o, uint32_t l)
: offset(o), length(l)
{
data = new char[1024];
printf("construct:%p\n", data);
}
~BackNode()
{
printf("destruct:%p\n", data);
delete[] data;
data = NULL;
}
BackNode(const BackNode& bn)
{
data = new char[1024];
memcpy(data, bn.data, 1024);
offset = bn.offset;
length = bn.length;
printf("BackNode(const BackNode& bn) construct:%p\n", data);
}
BackNode& operator=(const BackNode& bn) // for vector::insert and erase
{
data = new char[1024];
memcpy(data, bn.data, 1024);
offset = bn.offset;
length = bn.length;
printf("BackNode& operator=(const BackNode& bn) construct:%p\n", data);
return *this;
}
};
上面的代码中需要实现BackNode& operator=(const BackNode& bn),是因为对vector进行insert和erase操作时就需要用到赋值构造。例如main函数如下:
int main()
{
vector<BackNode> vb;
//vb.reserve(3);
vb.push_back({0, 0});
vb.push_back({0, 0});
vb.push_back({0, 0});
vb.erase(vb.begin());
vb.insert(vb.begin(), {0,0});
return 0;
}
是遵循RAII了,使用也方便了,不过内存拷贝和分配却变多了,如果能直接将data转移给新的BackNode就完美了,这个时候使用C++11里的move语义就能完美解决:
struct BackNode
{
BackNode(uint32_t o, uint32_t l)
: offset(o), length(l)
{
data = new char[1024];
printf("construct:%p\n", data);
}
~BackNode()
{
printf("destruct:%p\n", data);
delete[] data;
data = NULL;
}
BackNode(BackNode&& bn)
{
data = bn.data;
offset = bn.offset;
length = bn.length;
bn.data = NULL;
printf("BackNode(BackNode&& bn) construct:%p\n", data);
}
BackNode& operator=(BackNode&& bn)
{
cout << "operator&& construct" << endl;
data = bn.data;
offset = bn.offset;
length = bn.length;
bn.data = NULL;
printf("BackNode& operator=(BackNode&& bn) construct:%p\n", data);
return *this;
}
char *data;
uint32_t offset;
uint32_t length;
};
坦白来讲,我感觉move语义就是开放了一点写权限,以前的赋值构造和拷贝构造都是const的,不能改,现在的转移赋值构造和转移拷贝构造去掉了const,使得修改右值成为可能。