【问题标题】:C++11 / VS2010 : Returning containers of uncopyable but movable objectsC++11 / VS2010:返回不可复制但可移动对象的容器
【发布时间】:2012-07-21 10:48:27
【问题描述】:

考虑以下代码:

#include <vector>
#include <boost/noncopyable.hpp>

struct A : private boost::noncopyable
{
  A(int num, const std::string& name)
    : num(num),
      name(name)
  {
  }

  A(A&& other)
    : num(other.num),
      name(std::move(other.name))
  {
  }

  int num;
  std::string name;
};

std::vector<A> getVec()
{
  std::vector<A> vec;
  vec.emplace_back(A(3, "foo"));
  // vec.emplace_back(3, "foo"); not available yet in VS10?

  return vec; // error, copy ctor inaccessible
}

int main ( int argc, char* argv[] )
{
  // should call std::vector::vector(std::vector&& other)
  std::vector<A> vec = getVec();

  return 0;
}

这在 VS2010 下无法编译,因为显然 Anoncopyable,因此无法复制 std::vector&lt;A&gt;。因此,我无法从函数返回 std::vector&lt;A&gt;

但是,考虑到 RVO 的概念,我觉得这种事情是不可能的。如果此处应用了返回值优化,则可以省略复制构造,并且对 getVec() 的调用将是有效的。

那么正确的方法是什么?这在 VS2010 / C++11 中是否可能?

【问题讨论】:

  • RVO 并没有取消东西可复制的要求。
  • 您的成员初始化列表中数据成员的顺序(先是num,然后是name)与类底部数据成员的声明顺序不匹配(首先name,然后是 num)。一般来说,这会导致非常令人惊讶的错误,所以我总是会努力使订单保持一致。
  • 另外,std::vector&lt;A&gt; vec = getVec();不是一个赋值,而是一个初始化(准确地说是复制初始化)。
  • 尝试添加移动赋值运算符。这使它在 GCC 中为我编译。 (虽然我的错误在不同的地方)
  • @FredOverflow:您对这两件事是正确的,谢谢。现在已修复,因为它与实际问题没有任何关系。

标签: c++ visual-studio-2010 visual-c++ c++11 move-semantics


【解决方案1】:

如果return vec; 不编译,VS2010 还不完全支持移动语义。通常,如果从函数返回,自动变量会被隐式移动。使用return std::move(vec); 作为临时解决方法,并在脑海中记下以在将来摆脱std::move

完整的解释可以在标题“移出函数”下找到in this FAQ answer

此外,您的双参数构造函数会复制通过引用到常量传递的字符串参数。我建议改为按值取参数并将其移动到成员中:

A(int num, std::string name) : num(num), name(std::move(name)) { }

这样,您可以最大限度地减少必要副本的数量。详情请见Want Speed? Pass by Value

另外,由于你的移动构造函数没有做任何特别的事情,你可以default它:

A(A&& other) = default;

这使它在面对变化时更加稳健。错误很少隐藏在您不编写的代码中:)

【讨论】:

  • +1 指出这是一个 VS2010 问题。但是, std::move 不能解决问题。虽然,正如 Benjamin Lindley 在 cmets 中所说,添加移动赋值运算符确实可以为 VS2010 修复它。
  • @Opossum 真的吗?为什么分配很重要? std::vector&lt;A&gt; vec = getVec(); 不是作业。
  • 好吧,我猜向量的元素是使用移动赋值运算符移动的。添加A::A&amp; operator=(A&amp;&amp; other) 使其编译。
  • @FredOverflow:我不知道他遇到了什么错误,但我的错误是使用 emplace_back,而不是返回对象或 main 中的初始化。添加移动赋值运算符修复它。
  • @Opossum 好吧,那就等着吧,VS2012 也不带。它没有带来很多新东西,甚至没有像默认和删除函数或委托和继承构造函数这样相当简单的东西。
【解决方案2】:

但是考虑到 RVO 的概念,我觉得这种事情是不可能的。

省略是命名返回值优化等东西的总称,是一种优化。它不是必需的。规范允许,但即使允许,也不会强制任何实现这样做。

因此,为了在允许省略的编译器和不允许省略的编译器之间强制执行一致性,如果操作允许省略,编译器必须仍然验证考虑到代码的当前状态,省略复制/移动将是可能的。因此,如果无法访问复制/移动构造函数,即使编译器不会实际调用它,操作也会失败。

在这种情况下,Visual Studio 2010 在这方面似乎有点困惑。它确实认识到return vec; 应该从vec 移动。但是,VS2010 的std::vector 实现似乎需要一个移动赋值运算符来移动;如果没有,它将尝试复制。

【讨论】:

  • 错了,正常情况下vector应该在函数中自动移动。
  • 但是移动构造函数可以访问的。
猜你喜欢
  • 1970-01-01
  • 2013-04-08
  • 2023-03-07
  • 2021-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多