【问题标题】:Are move semantics incomplete?移动语义是否不完整?
【发布时间】:2014-01-31 20:54:16
【问题描述】:

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

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

将 const 不可复制对象分配给内存区域时会出现问题。这样的对象无法以任何方式恢复。

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

我建议 moving 析构函数可能是移动语义模型的一个有价值的补充。

考虑一个在 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 实际上更有意义。析构函数不必修改任何内容,只需将对象销毁,就好像它不再是它所控制的任何资源的有效句柄一样。

【问题讨论】:

  • Const 对象在构造过程中也不是const
  • 更合适的论坛将是此页面上的前 2 个论坛之一:isocpp.org/forums
  • 基本上它只会复制它需要的东西(这就是移动在c ++ 11中的意思)到一个新对象中,然后像正常移动一样销毁旧对象,但是发生但是对于非临时工
  • @BenjaminLindley 是的,我想我不想讨论“不可能”或“类似于这个提议”
  • @BenjaminLindley 我在别处发布了这个问题,时间会证明这里是否有人想删除它,或者回答它(或者,你知道,赞成它..)

标签: c++ c++11 language-features move-semantics


【解决方案1】:

对不起,前提是有缺陷的。

unordered_set 实际上并不包含 const 对象。它只是不给你写访问包含的元素。这只是访问器的属性。

可以添加一个key erase(iterator) 函数,它只是将元素移出到一个临时位置。我不知道你为什么想要一个key&&

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

【讨论】:

  • 当然它没有任何意义,这不是问题所在。 “可以添加一个键擦除(迭代器)函数,它只是将元素移出到一个临时位置。”不,您不能从 const 移动,除非使用 const move 运算符。这样的运算符等效于 const 复制运算符。问题是它可以在代码中的任何地方调用。通常,不可复制的类用于发出唯一对象的信号——“移动临时”如何工作?当从对象移动的原始对象被破坏时会发生什么?它不能被修改,因此它会释放它所持有的任何资源。
  • 基本上,唯一的解决方案是允许访问内存的句柄(各有利弊,而且非常不通用,并且无法解决删除对象的问题(请参阅here 了解其他人对类似主题的想法。))或提供一些允许在所述对象生命周期结束时从对象构造的原语(这是防止重复删除的唯一方法,因为移动会引起) 和我的建议这样一种可能的原语(只是一个例子,我怀疑有更好的方法)
  • 而且 unordered_set 只是一个例子,(它可以有 const 对象)并且从一个对象移动会使哈希无效(因此从外部角度来看它必须有 const 键)
  • @user3125280:集合本身不是 const (显然,因为 erase 不是 const),它的成员也不是(你一直声称它们是,请提供实际证据。忽略访问者)。然而,这是您为如此重要的补充提供的唯一原因。抱歉,我的书中没有足够的理由。
  • "set 本身不是 const" true(尽管您可以通过常量指针删除,通过 const_iterator 擦除等) - 考虑从一个键移动。移动创建并返回一个新密钥。旧密钥现在已修改。至关重要的是,前一个键可能有一个新的散列。容器不知道这一点,并且失去了对元素的控制。目前,除非使用 const 复制构造函数,否则我们无法删除它,同意吗?但是随后未更改的元素仍然通过其旧的析构函数被删除(假设它拥有内存或其他东西)
【解决方案2】:

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

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

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

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

我的立场是正确的:我上面描述的功能是用P0083R3 投票进入 C++17 的。感谢 Cosme 在下面的 cmets 中提醒我这一点。

【讨论】:

  • 谢谢,这就是我正在寻找的答案。我知道可以更改库以解决这个特定示例,但我希望阅读此文的人意识到这只是一个特定示例 - 是的,“破坏性移动语义”正是我试图讨论的问题
  • 上述提案的最后一次修改:P0083R3
【解决方案3】:

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

抱歉,我认为将其设为const 的全部要点是为了防止这种情况发生。

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

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

见下面的 cmets...

【讨论】:

  • 是的,(调用不同的然后正常的析构函数,所以正常的不会被调用两次)。实际上,这并不是一个真正的漏洞,因为您已经可以在 c++ 中做到这一点。这是未定义的行为并违反了一些规则。我将不得不环顾四周才能给出明确的答复,但我认为删除对象意味着在该地址访问对象不再有效(在销毁对象时,无论如何您都必须使用黑客来保留该内存那里)。即使该对象是 const 限定的。它只是未定义的。让我举一个ideone的例子..
  • I tried 但它似乎并没有欺骗编译器(因为它是非法的)。我确定您知道 C++ 新手经常犯错误,即返回对临时对象的引用。这是同样的情况。一旦对象超出范围或被指针(或假设的析构函数)删除,无论如何访问它都是无效的。 const 不会使对象永久化,仅在其生命周期内只读。
  • @user3125280:嗯,我很确定有合法的方法可以做到这一点,但有趣的是,我认为问题现在就在那里,可能与你的提议无关。例如 struct S { char x; }; char buf[sizeof(S)]; S const *p = new (buf) const S(); foo(p); S moved(p-&gt;~S()); new (buf) const S(moved.~S()); 哎呀,我们刚刚修改了 p 的目标,即使它被构造为 const 对象...也许你的建议没有毕竟不影响吗?
  • @Mehrad 这就是我的意思 - 代码未定义,原始 const S 已被破坏。尝试通过 P 访问它是错误的,即使它使用新的 const S 作为别名。这是编程错误,而不是 c++ 错误。对已销毁对象的访问始终未定义。
  • @user3125280:真的没有定义吗?这是 POD,析构函数和构造函数是微不足道的,所以这里没有销毁或构造的概念。我很确定这是合法的,不是吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-04
  • 2013-01-18
  • 2020-11-07
相关资源
最近更新 更多