复制构造函数优于移动构造函数?

时间:2018-07-18 18:32:35

标签: c++ c++11

我正在研究一个简单的JSON解析器,很有趣,并且我有自己的值类型:

typedef enum {
    JSON_NULL,
    JSON_NUMBER,
    JSON_STRING,
    JSON_ARRAY,
    JSON_OBJECT,
    JSON_BOOLEAN
} json_type_t;

// datatype for json value
struct json_value {
    using arr_type = vector<json_value>;
    using obj_pair = pair<string, json_value>;
    using obj_type = unordered_map<string, json_value>;

    // constructors 
    json_value()
        : type(JSON_NULL) {}

    json_value(json_type_t type)
        : type(type) {
        switch(type) {
            case JSON_STRING:  str = new string;   break;
            case JSON_ARRAY:   arr = new arr_type; break;
            case JSON_OBJECT:  obj = new obj_type; break;
            default:
                break;
        }
    }

    // copy construct
    json_value(const json_value& other) {
        printf("copying json value\n");
        if (other.type != JSON_NULL) {
            type = other.type;
            switch(type) {
                case JSON_NULL:                                    return;
                case JSON_NUMBER:  num = other.num;                return;
                case JSON_BOOLEAN: val = other.val;                return;         
                case JSON_STRING:  str = new string  (*other.str); return;
                case JSON_ARRAY:   arr = new arr_type(*other.arr); return;
                case JSON_OBJECT:  obj = new obj_type(*other.obj); return;
            }
        }
    }

    // move construct
    json_value(json_value&& other) {
        type = other.type;
        switch(type) {
            case JSON_NULL:                     break;
            case JSON_NUMBER:  num = other.num; break;
            case JSON_BOOLEAN: val = other.val; break; 
            case JSON_STRING:  str = other.str; other.str = nullptr; break;
            case JSON_ARRAY:   arr = other.arr; other.arr = nullptr; break;
            case JSON_OBJECT:  obj = other.obj; other.obj = nullptr; break;
        }
    }

    // assignment operator copy/swap idiom
    json_value& operator =(json_value other) {
        destroy();
        type = other.type;
        switch(type) {
            case JSON_NULL:                     break;
            case JSON_NUMBER:  num = other.num; break;
            case JSON_BOOLEAN: val = other.val; break; 
            case JSON_STRING:  str = other.str; other.str = nullptr; break;
            case JSON_ARRAY:   arr = other.arr; other.arr = nullptr; break;
            case JSON_OBJECT:  obj = other.obj; other.obj = nullptr; break;
        }
        return *this;
    }

    // destructor
    ~json_value() {
        destroy();
    }

    // type of value and union to hold data
    json_type_t type = JSON_NULL;
    union { 
        bool      val;
        double    num;
        string   *str;
        arr_type *arr;
        obj_type *obj;
    };

private:
    // cleanup our memory
    void destroy() { 
        switch(type) {
            case JSON_NULL:    break;
            case JSON_NUMBER:  break;
            case JSON_BOOLEAN: break; 
            case JSON_STRING:  delete str; break;
            case JSON_ARRAY:   delete arr; break;
            case JSON_OBJECT:  delete obj; break;
        }
        type = JSON_NULL;
    }
};

我已经编写了正确的复制/移动构造函数和赋值运算符。我的问题是,在运行特定基准测试时,解析器大约需要40毫秒。为了优化一点,我注释掉了拷贝构造函数以确保我没有制作任何不必要的拷贝。果然,我的代码仍然可以编译,表明move构造函数已经足够了, 快了25%!

插入复制构造函数,可以看到确实在调用它,但是正如我所显示的,move构造函数就足够了。

所以,我的问题是,在哪种情况下,复制构造函数比move构造函数更可取?我该如何找到发生的地方?

1 个答案:

答案 0 :(得分:2)

标准容器都尝试使用strong exception guarantee,这意味着如果引发异常,就好像什么也没发生。

std::vector为例。为了保持此保证,只有在保证移动不会抛出的情况下,它才能使用move构造函数。考虑矢量需要调整其缓冲区大小的情况:

d // new element to push_back
[a][b][c] // old, filled buffer
[ ][ ][ ][ ][ ][ ] // new, empty buffer

将新元素移到适当位置就算是个问题,即使它抛出了,因为我们仍然拥有可以使用的旧缓冲区:

[a][b][c]
[ ][ ][ ][d][ ][ ]

但是当我们将旧缓冲区的元素移到新缓冲区中时,如果将中间的元素扔进去会发生什么?

[ ][#][c]
[a][#][ ][d][ ][ ]

我们不知道抛出b时所处的状态,那么如何重新创建旧状态?即使我们能够复活b,我们也不能只是将先前的元素移回去,因为移动这些元素也可能会抛出。

如果我们退回副本,则可以随时丢弃新缓冲区来退出。

因此,为了保持强大的异常保证,除非move构造函数为std::vector,否则noexcept不能移动。


将移动操作声明为noexcept对于标准容器使用它们是必要的。在大多数情况下,移动构造函数和移动分配可以为noexcept,因此在以下情况下要声明它们:

json_value(json_value&& other) noexcept {
    // ...
}

json_value& operator=(json_value&& other) noexcept {
    // ...
}