【问题标题】:Proper way (move semantics) to return a std::vector from function calling in C++11从 C++11 中的函数调用返回 std::vector 的正确方法(移动语义)
【发布时间】:2011-09-06 21:10:40
【问题描述】:

我想填充 std::vector(或其他一些 STL 容器):

class Foo {
public:
  Foo(int _n, const Bar &_m);
private:
  std::vector<Foo> fooes_;
}

1.好看的ctor,性能昂贵

std::vector<Foo> get_vector(int _n, const Bar &_m) {
  std::vector<Foo> ret;
  ... // filling ret depending from arguments
  return ret;
}

Foo::Foo(int _n, const Bar &_m) : fooes_(get_vector(_n, _m) {}

2.更好的性能,更差的 ctor

void fill_vector(int _n, const Bar &_m, std::vector<Foo> &_ret) {
  ... // filling ret depending from arguments
}

Foo::Foo(int _n, const Bar &_m) { fill_vector(_n, _m, fooes_); }

是否可以使用 C++0x(移动语义功能等)重写第一个示例中的 get_vector 函数以避免冗余复制和构造函数调用?

【问题讨论】:

  • 您能否澄清您的_m 参数是否会被这些函数修改?在这些函数中使用_m 做了什么?是复制到get_vector吗?
  • @Johannes - 感谢您删除的答案 (+1)。我添加了 2 个不同的论点只是为了说明。我对它们的性质不太感兴趣。没错,最好加个const,以免造成混乱。我不太了解右值引用。是Bar 必须在Foo::Foo(int _n, Bar _m) fooes_(get_vector(_n, move(_m)) {} 中移动ctor 吗?
  • 这里没有描述 RVO、移动等的所有细节。这里是查看详细信息的文章的链接:cpp-next.com/archive/2009/08/want-speed-pass-by-value

标签: c++ stl c++11 move-semantics return-value-optimization


【解决方案1】:

如果您使用的是与 C++0x 兼容的编译器和标准库,您可以从第一个示例中获得更好的性能无需执行任何操作。 get_vector(_n, _m) 的返回值是一个临时值,std::vector 的移动构造函数(一个采用右值引用的构造函数)将被自动调用,而无需您做进一步的工作。

一般来说,非库编写者不需要直接使用右值引用;您将自动获得相当一部分的好处。

【讨论】:

    【解决方案2】:

    我相信 (1) 和 (2) 即使没有 C++0x 也具有相同的性能,只要您的编译器执行命名返回值优化,我相信大多数人都会这样做。也不应该做任何复制,也不应该移动。

    如果我错了,请纠正我,因为如果这样我误解了 NRVO。

    【讨论】:

    • 没错。即使在 C++0x 中,编译器也会首先考虑 RVO,如果不能,则考虑移动对象,如果不允许,则将复制作为最后的手段。
    【解决方案3】:

    在您正在考虑的特定情况下,第一个实现与第二个实现一样有效。编译器会将get_vector函数中ret的副本优化为返回值,并使用移动语义将向量的所有权转移给容器类。向量中的移动构造需要(取决于实现,但一个很好的近似值)3 个指针副本,与容器中元素的数量和大小无关。将向量作为要修改的引用传递需要一个指针副本(再次近似成本),但是您对向量执行的任何操作都将支配任一选项的成本。

    在某些非常特殊的情况下,将向量传递给函数进行修改可能会更快,但这些情况很少见,并且与域相关而不是向量本身。忽略这一点,代码首先是为了可维护性,如果程序很慢,请分析,确定程序的成本在哪里,然后才考虑优化。有趣的是,一旦您进行了概要分析,您可能就知道瓶颈是什么,这意味着您将对要更改的内容有所提示。

    【讨论】:

    • 关于性能,这取决于:如果你写像std::vector&lt;Foo&gt; v(get_vector(...)) 这样的东西,编译器应该能够优化复制出来。如果您在现有的 std::vector&lt;Foo&gt; 上执行 v = get_vector(...),可能不会。
    • @James Kanze:这就是评论的根本原因传递向量时的某些特定情况可能会更快。在容器中执行clear() 不会释放内存,如果新元素适合同一个容器,则也不需要获取内存,因此效率更高。但谈到对象创建(如问题中成员初始化的情况),它们将是等价的。
    • @David Rodriguez 是否。唯一可以确定的方法是尝试两者并进行测量(除非确实存在性能问题,否则不值得费心)。在一种或两种特殊情况下,我发现将变量移出循环会更快,原因是您提到的:一旦分配了内存,它就不会被释放,因此您最终会减少重新分配。 (但这不是通用的。)我的建议是只返回容器,直到它成为性能问题,然后尝试不同的解决方案,找到解决问题的解决方案。
    • @James :如果你对现有的std::vector&lt;Foo&gt; 执行v = get_vector(...),则没有副本;这个调用的vector&lt;&gt; 的移动赋值运算符,因为get_vector 返回一个右值。我不明白你在说什么......
    • @ildjarn 不是我的编译器,它没有。大多数编译器都没有移动语义,我使用的那些即使它们可用也不会在标准库中使用它们。
    猜你喜欢
    • 1970-01-01
    • 2011-11-25
    • 2013-04-12
    • 1970-01-01
    • 2016-11-11
    • 1970-01-01
    • 2021-12-12
    • 2017-05-01
    • 1970-01-01
    相关资源
    最近更新 更多