C++11中的move语义

以前就知道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,使得修改右值成为可能。

发表于 2015年08月20日 17:31   评论:0   阅读:2307  



回到顶部

首页 | 关于我 | 关于本站 | 站内留言 | rss
python logo   django logo   tornado logo