移动语义不完整吗?

Are move semantics incomplete?

本文关键字:语义 移动      更新时间:2023-10-16

在复制效率低下的情况下,移动语义取代复制语义。复制语义完全处理可复制的对象,包括常量对象。

在 c++11 中已经存在无数不可复制的对象,例如 std::unique_ptr。这些对象完全依赖于移动语义,因为从对象移动允许使其无效。这对于像RAII这样的流行设计模式很重要(恕我直言)。

将常量不可复制对象分配给内存区域时出现问题。此类对象无法以任何方式恢复。

这在对象的生命周期中显然很重要,因为它的恒定性。然而,在其生存期结束时,当调用析构函数时,(不存在的)对象短暂地是非常量。

我建议移动析构函数可能是移动语义模型的宝贵补充。

考虑在unordered_set中使用unique_ptr的简单情况。您可以使用移动构造函数(或构造"emplace")insert到此集合中,但是如果您想将此指针移动到另一个unordered_set(即保持恒定),则是不可能的。

必不可少,有iterator insert((possibly const) key&&)但没有const key&& erase(iterator).事实上,这是不可能的。容器只能扩展为返回一些指向键的指针,然后忘记它。

移动析构函数可以解决这个问题,即const MyClass&& ~MyClass(),因为它只会在销毁期间违反 const(当编译器认为对象无论如何都是无效的时)。

编辑:我应该指出const MyClass&& ~MyClass() const实际上更有意义。析构函数不必修改任何hting,只需销毁对象,就好像它不再是它控制的任何资源的有效句柄一样。

恕我直言,您已经确定了真正的需求。

您的解决方案听起来很像我所说的破坏性移动语义。 这种可能性在原始移动语义提案中进行了描述。 我认为这样的设计是可能的,尽管它并非没有问题。 据我所知,没有人在标准委员会中从事这一领域的工作。

有一些更简单的方法可以从不需要更改语言的关联容器中提取仅移动类型(除了允许类型双关语而没有未定义的行为)。

N3645 是仅限库的建议,它将嵌套的node_ptr类型放入每个容器中。node_ptr很像unique_ptr. 它具有关联容器中节点的唯一所有权。 但是,当您取消引用它时,您将获得对节点中value_type而不是节点本身的非常量访问权限。extractinsert成员被添加到关联容器中,允许在容器中插入和删除节点(由node_ptr拥有)。

您可以使用它从容器中删除节点,然后将仅移动类型移出节点,并让~node_ptr()清理完节点。 本文包含此示例来演示此功能:

set<move_only_type> s;
s.emplace(...);
move_only_type mot = move(*s.extract(s.begin())); // extract, move, deallocate node

请注意,s.extractnoexcept的,当然~node_ptr()也是如此。 如果move_only_type的移动构造是noexcept的,那么这整个操作就是noexcept。 否则,如果移动结构抛出,则set将保留,就好像该项目已从set中删除一样。

目前,N3645没有取得任何进展。 它尚未被投票纳入工作草案,我也不相信它永远不会被投票。

更新 C++17

我的立场是正确的:我上面描述的功能被投票到C++17与P0083R3。 感谢 Cosme 在下面的评论中提醒我这一点。

抱歉,前提是有缺陷的。

unordered_set实际上并不包含const对象。它只是不授予您对所包含元素的写入访问权限。这仅是访问器的属性。

可以添加一个key erase(iterator)函数,该函数只是将元素移出到临时元素。我不确定你为什么要在那里key&&

至于const MyClass&& ~MyClass() const,这没有意义,原因有三:dtor既没有返回类型也没有CV分类,也没有为它们完成重载解析。

所以基本上你是说应该可以将一个const对象移动到另一个const对象中并销毁原始对象?

对不起,但我认为让它const的全部意义在于防止这种情况。

否则,它会形成一个漏洞:你可以销毁 - 将一个const对象移出其内存位置,然后你销毁 - 将另一个const对象移动到第一个对象的内存位置(通过放置new)。

现在对象已经改变了,即使它是const......所以本质上const是没有用的。

请参阅下面的评论...