【问题标题】:C++ "move from" containerC++“移出”容器
【发布时间】:2011-01-09 17:18:49
【问题描述】:

在 C++11 中,当我们想要将值移动(破坏性复制)到容器中时,我们可以使用 std::move 来提高效率:

SomeExpensiveType x = /* ... */;
vec.push_back(std::move(x));

但我找不到任何相反的方向。我的意思是这样的:

SomeExpensiveType x = vec.back(); // copy!
vec.pop_back(); // argh

这在适配器上更频繁(复制弹出),例如stack。这样的事情是否存在:

SomeExpensiveType x = vec.move_back(); // move and pop

为了避免复制?这已经存在了吗?我在n3000中找不到类似的东西。

我有一种感觉我错过了一些非常明显的东西(比如它的不必要性),所以我为“ru dum”做好了准备。 :3

【问题讨论】:

  • move 方法对超出范围的变量有什么影响?例如,如果我创建一个对象,将其添加到成员容器中,则该对象超出范围......由于没有复制,成员容器中的对象是否仍然定义?
  • 是的,它被移到了容器中。如果你不确定它们是如何工作的,你会想要谷歌右值引用。
  • 非常感谢...我将不得不再深入研究一下,哈哈。从表面上看,这似乎会导致问题。

标签: c++ c++11 containers rvalue-reference


【解决方案1】:

我可能完全错了,但这不是你想要的

SomeExpensiveType x = std::move( vec.back() ); vec.pop_back();

假设 SomeExpensiveType 有一个移动构造函数。 (对于您的情况显然是正确的)

【讨论】:

  • 我想到了这一点,但我对从容器中移出这样的东西持谨慎态度。现在我想多了,它似乎完全合法......:|
  • @Scary: back 返回一个引用,使用 std::move 会将其转换为右值引用。
  • @GMan:不用担心,move 是类提供的服务,而不是容器。容器中的对象仍然保证保持有效。
  • @Jerry:不,“移出”对象仍然有效。它存在,只是通常重置为非常简单的状态。它的生命周期没有过期,它的析构函数也没有被调用,但它被调用。就语言而言,该对象是完全有效的
  • @Jerry @jalf @Daniel:应该注意标准所说的已经改变了。从 FCD 中,表 34 和 36(从 33 移动,不相关)说:“注意:rv 仍然是一个有效的对象。它的状态是未指定的——结束注释]”所以它很明确:一个移动的对象需要能够安全地破坏,但没有指定其他内容。很高兴他们澄清了这一点。
【解决方案2】:

为了完整性(以及任何在没有 C++1x 编译器的情况下偶然发现这个问题的人),今天已经存在的替代方案:

SomeExpensiveType x;
std::swap(x, vec.back()); 
vec.pop_back();

它只需要 std::swap 的特化才能存在于元素类型。

【讨论】:

  • 除非不明确使用 std::swap (因为它不能正确地专门用于许多类型);使用带有不合格交换调用的 using 声明。
  • @Roger Pate - 或使用 boost::swap 获得相同的结果。
  • 我宁愿只输入一行,也不愿担心谁会抱怨 Boost,因为有人不可避免地会这样做。 :)
  • @sellibitze ...除非您意识到现在已经是 2010 年并且标准还不完整,这意味着 C++0x 中“0”的原始含义已过时。我仍然会像其他人一样称它为 C++0x,但你可以争论任何一种方式。
  • 如果我感觉不那么乐观,有时我会使用 C++2x。
【解决方案3】:
template<class C>
auto pop_back(C& c) -> typename std::decay<decltype(c.back())>::type
{
  auto value (std::move(c.back()));
  c.pop_back();
  return value;  // also uses move semantics, implicitly
  // RVO still applies to reduce the two moves to one
}

【讨论】:

  • 我发现 std::decay 比 std::remove_reference 更合适。例如,它将“const MyClass&”转换为“MyClass”(删除 const)。
  • 我不是 100% 确定,但 typename-&gt; 之后可能会丢失
  • 确实如此。为什么看起来这应该简单得多?
  • 在某些情况下 typename 不是必需的(例如,在基类列表中)。由于只有一个类型可以跟随箭头,所以 typename 是相当多余的。我只是不确定这个上下文是否需要类型名。
  • @sellibitze:我对草案和当前 gcc 的解释同意:此处需要。
【解决方案4】:

通常对于昂贵的类型,我认为您希望将包装类或智能指针推送到容器中。这样,您就可以避免昂贵的副本,而只做智能指针或包装类的廉价副本。如果你愿意,你也可以使用原始指针哈哈。

class ExpensiveWrapper
{
public:
   ExpensiveWrapper(ExpensiveClass* in) { mPtr = in; }

   // copy constructors here....

private:
   ExpensiveWrapper* mPtr;

};

【讨论】:

  • move语义的一点就是去掉这个方法。容器只会move它们的内容而不是复制它们。
猜你喜欢
  • 2017-09-10
  • 2018-01-06
  • 1970-01-01
  • 1970-01-01
  • 2013-01-03
  • 1970-01-01
  • 2023-03-26
  • 1970-01-01
  • 2015-01-09
相关资源
最近更新 更多