Rom*_*NNN 5 c++ move mutable copy-constructor move-semantics
我正在编写一些像 stl 这样的自定义库,但在 ctors 中没有分配,并且在资源拥有类中禁用了复制 ctors(因为环境不支持异常,堆上的所有分配都需要通过 retcode 检查)。
所以我从https://github.com/Kronuz/cpp-btree/移植 btree ,并且由于代码问题和我的方法一起被卡住了。
value_type与所有 stl 实现一样,是std::pair< const Key, Value>。const限定符使整个对隐式不可移动。
所以代码
x->construct_value(j, std::move(fields_.values[i]));
Run Code Online (Sandbox Code Playgroud)
( https://github.com/Kronuz/cpp-btree/blob/35ac0ec96f1fca1463765f169390059ab82d3aac/btree/btree.h#L615 )
实际上不会移动对象(返回 T& 而不是 T&&)并且
new (v) value_type(std::forward<Args>(args)...);
Run Code Online (Sandbox Code Playgroud)
( https://github.com/Kronuz/cpp-btree/blob/35ac0ec96f1fca1463765f169390059ab82d3aac/btree/btree.h#L883 )
正确地无法通过复制构造函数构造对。
有没有办法在没有或绕过复制移动语义的情况下在内存中重定位对象?当然,简单的解决方法是使std::pair< Key , Value>具有可变键,但这并不完全相同。
我找到了 Arthur O'Dwyer 提出的“trivially_realocable”提议,我得出的结论是,这正是关于这种情况的。( https://quuxplusone.github.io/blog/2018/07/18/annoucing-trivially-relocatable/ )
阿瑟·奥德怀尔来了!:)
\n事实上,在标准 C++ 中没有办法做你想做的事。事实上,在P1144 trivial-relocation \xe2\x80\x93land中也没有办法做你想做的事情。本质上,你有这样的类型:
\nusing Trouble = std::pair<const std::unique_ptr<int>, int>;\nRun Code Online (Sandbox Code Playgroud)\n它不可复制(因为unique_ptr不可复制),但也不可移动(因为const unique_ptr不可移动)。
您要对其执行的操作称为“重定位”。您已将其命名为move_value,但您应该立即将其重命名为relocate_value,以便代码的读者知道您的意思不是 C++ 意义上的“移动”;而是指“移动”。你的意思是“搬迁”!
在P1144-land中,“重新定位”实际上是“移动然后摧毁源头”的同义词。所以 P1144 认为你的类型Trouble是不可重定位的,因为它是不可移动的。P1144并没有尝试引入一个新的动词“relocate”;它只是为在当前标准下当操作很简单时已经可重定位的类型提供一个高性能的代码路径。不可能创建一个 P1144-trivially-relocatable 的类型而不可移动和破坏,就像今天不可能创建一个编译器将识别为“trivially copyable”的类型一样但它也不是可复制分配的。各个组件操作的存在是琐碎性特征的先决条件。(我希望这有助于解释为什么 P1144 这样做!)
重申一下:如果不超出标准,就无法解决问题。
\n那么,我将如何设计解决您的问题的方案呢?
\n第一步,我会用我知道的最好方式编写客户端代码:
\n// Relocate value i in this node to value j in node x.\nvoid relocate_value(int i, btree_node* x, int j) {\n assert(x != this || i != j);\n assert(0 <= i && i <= fields_.count);\n assert(0 <= j && j <= x->fields_.count);\n if constexpr (my::is_trivially_relocatable_v<value_type>) {\n memcpy(&x->fields_.values[j], &fields_.values[i], sizeof(value_type));\n } else {\n ::new (&x->fields_.values[j]) value_type(std::move(fields_.values[i]));\n fields_.values[i].~value_type();\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n请注意,我已在此处内联了x->construct_value和destroy_value内容。在现实生活中,我可能会将这些东西分解回助手中。P1144 命名这个助手std::relocate_at:
// Relocate value i in this node to value j in node x.\nvoid relocate_value(int i, btree_node* x, int j) {\n assert(x != this || i != j);\n assert(0 <= i && i <= fields_.count);\n assert(0 <= j && j <= x->fields_.count);\n my::relocate_at(&fields_.values[i], &x->fields_.values[j]);\n}\nRun Code Online (Sandbox Code Playgroud)\n好的,这就是客户端的情况。现在我们在保证人方面该怎么做?在 P1144 领域,编译器负责确定哪些类型可以轻松重定位。但是 (A) 我们没有 P1144 的编译器支持,并且 (B) 我们实际上想要一个稍微不同的定义,因为我们希望被const unique_ptr认为是简单可重定位的。因此,我们将创建一个自定义点,类型作者可以自定义:
template<class T, class = void>\nstruct is_trivially_relocatable : std::is_trivially_copyable<T> {};\ntemplate<class T>\ninline constexpr bool is_trivially_relocatable_v = is_trivially_relocatable<T>::value;\nRun Code Online (Sandbox Code Playgroud)\n请注意“这是通过模板(部分)专业化进行定制的一个奇怪的技巧”的额外参数(属于类模板,而不是变量模板)。这让我们可以编写自定义内容,例如
\ntemplate<class A, class B, class = std::enable_if_t<my::is_trivially_relocatable_v<std::remove_const_t<A>> && my::is_trivially_relocatable_v<std::remove_const_t<B>>>>\nstruct is_trivially_relocatable<std::pair<A, B>> : std::true_type {};\nRun Code Online (Sandbox Code Playgroud)\n这里实际的工程问题是:谁的责任是定义标准库类型的专业化,my::is_trivially_relocatable例如std::pair等?如果您和您的同事都为同一类型定义了专业化,那么您的程序就格式不正确。
此外,事实证明,专业编码员非常不擅长猜测复杂的 STL 类型(例如std::string和 )std::list在实践中是否可以轻松重定位。因此,给你和你的同事这种权力实际上是在到处寻找微妙的错误。(例如,参见《Folly》第 889 期。)
最后,如果您和您的同事都忘记为您的可重定位类型添加专门化Widget(碰巧Widget也是可移动和可破坏的),那么您的代码将默默地缺少这种优化 \xe2\x80\x94 这对于很多人。如果我们要允许这种优化,我们应该确保它自动发生,并且不会仅仅因为有人拼错了模板专业化而感到悲观。
总而言之:
\n您调用了该操作move_value,但您应该调用它relocate_value。
P1144 对您的实际用例没有帮助const unique_ptr;它仅优化 C++20 中已经可移动的类型,而const unique_ptr不是不可移动的类型。
您可以使用自定义点模拟 P1144。您会遇到很多好伙伴(Facebook Folly、Bloomberg BSL、EASTL),但需要考虑巨大的可维护性缺点。
\n