我正在研究一个简单的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构造函数更可取?我该如何找到发生的地方?
答案 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 {
// ...
}