复制构造函数优先于移动构造函数
Copy constructor being preferred over move constructor?
为了好玩,我正在开发一个简单的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 valuen");
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;
}
};
我已经编写了正确的复制/移动构造函数和赋值运算符。我的问题是,当运行一个特定的基准测试时,解析器大约需要40ms。为了进行一些优化,我注释掉了副本构造函数,以确保我没有制作任何不必要的副本。果不其然,我的代码仍然可以编译,这表明move构造函数已经足够了,和的速度快了25%!
通过检测复制构造函数,我可以看到它确实被调用了,但正如我所展示的,移动构造函数就足够了。
所以,我的问题是,在什么情况下,复制构造函数比移动构造函数更受欢迎,我如何才能找到发生这种情况的地方?
标准容器都试图具有强异常保证,这意味着如果抛出异常,就好像什么都没发生一样。
以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
,我们也不能只是把之前的元素移回去,因为移动这些元素也可能会投掷。
如果我们退回到一个副本,我们可以随时通过丢弃新的缓冲区来退回。
因此,为了保持强异常保证,除非移动构造函数是noexcept
,否则std::vector
不能移动。
将移动操作声明为noexcept
对于标准容器使用它们是必要的。大多数时候,move构造函数和move赋值可以是noexcept
,所以当它们是时,就这样声明它们
json_value(json_value&& other) noexcept {
// ...
}
json_value& operator=(json_value&& other) noexcept {
// ...
}
相关文章:
- 为什么不调用移动构造函数?(默认情况下只有构造器,没有别的)
- std::vector::p ush_back() 不会在 MSVC 上编译具有已删除移动构造函数的对象
- 仅包含可移动 std::map 的类的移动构造函数不起作用
- 为什么调用复制构造函数而不是移动构造函数?
- 基类中的默认析构函数禁用子类中的移动构造函数(如果有成员)
- 从具有按值捕获的 lambda 移动构造 std::函数时,移动构造函数调用两次
- 具有已删除移动和复制构造函数的类的就地构造
- 移动构造函数和右值引用
- 使用移动调用对等构造函数unique_ptr默认构造函数
- 为什么 std::memmove 中联合的默认非平凡移动构造函数C++?
- 具有专用化的模板类中的可靠条件复制和移动构造函数
- 构造函数采用std::string_view与std::string并移动
- C++:为什么不调用移动构造函数?
- 了解构造函数在移动、复制、赋值语义中的行为
- 没有移动的构造函数移动课程
- 引用绑定和复制构造函数/移动构造函数
- 构造函数移动
- C++ 向量实现 - 移动构造函数 - 移动与前进
- 我真的必须取消移动构造函数/移动结构中的所有成员还是只是指针
- 将类(没有默认构造函数)移动到另一个类的move构造函数中