【问题标题】:Is pass by value that much faster?传值快得多吗?
【发布时间】:2015-02-26 02:27:01
【问题描述】:

我听说由于引入了移动语义,您应该始终更喜欢 C++11 中的“按值传递”。我想看看炒作的全部内容并构建了一个测试用例。首先是我的班级:

struct MyClass {
    MyClass() { }
    MyClass(const MyClass&) { std::cout << "Copy construct" << std::endl; }
    MyClass(MyClass&&) { std::cout << "Move construct" << std::endl; }
    ~MyClass() { }
};

以及测试工具:

class Test
{
public:
    void pass_by_lvalue_ref(const MyClass& myClass)
    {
        _MyClass.push_back(myClass);
    }

    void pass_by_rvalue_ref(MyClass&& myClass)
    {
        _MyClass.push_back(std::move(myClass));
    }

    void pass_by_value(MyClass myClass)
    {
        _MyClass.push_back(std::move(myClass));
    }
private:
    std::vector<MyClass> _MyClass;
};

据推测,pass_by_value 应该优于pass_by_lvalue_refpass_by_rvalue_ref(一起,而不是单独)。

int main()
{
    MyClass myClass;
    Test Test;
    std::cout << "--lvalue_ref--\n";
    Test.pass_by_lvalue_ref(myClass);
    std::cout << "--rvalue_ref--\n";
    Test.pass_by_rvalue_ref(MyClass{});
    std::cout << "--value - lvalue--\n";
    Test.pass_by_value(myClass);
    std::cout << "--value - rvalue--\n";
    Test.pass_by_value(MyClass{});
}

这是我在 GCC 4.9.2 上使用-O2 的输出:

--lvalue_ref--
Copy construct
--rvalue_ref--
Move construct
Copy construct
--value - lvalue--
Copy construct
Move construct
Copy construct
Copy construct
--value - rvalue--
Move construct

如您所见,非pass_by_value 函数总共需要 2 个复制构造和 1 个移动构造。 pass_by_value 函数总共需要 3 个复制构造和 2 个移动构造。看来,果然还是要复制对象,那为什么大家都说按值传递呢?

【问题讨论】:

  • 测试工具坏了。您的一些push_backs 会触发重新分配,而另一些则不会。按值传递并不是一对重载的性能提升。这是一个可维护性的增益。
  • 作为 T.C.说,确保您的 vector 足够大以防止重新分配 - coliru.stacked-crooked.com/a/e424a3073bdc727c 另外,这是您的 another version of your example 导致重新分配,但在发生这种情况时避免复制构造,注意区别吗? (我做了MyClass的移动构造函数noexcept
  • _Typename 作为变量的名称是一个非常可怕的命名约定......
  • @MattMcNabb 更不用说它使用了一个保留的标识符......

标签: c++ c++11 move-semantics pass-by-value


【解决方案1】:

首先,您的报告完全有缺陷。您的每个函数都推回到同一个向量。当该向量用完容量时(这取决于您到目前为止插入了多少项目),它将触发重新分配,这将需要比不触发分配的插入更多的移动和/或副本.

其次,std::vector::push_back 有一个strong exception safety guarantee。因此,如果您的移动构造函数不是 noexcept,它将不会使用它(除非该类是不可复制的)。它将使用复制构造函数。

第三,

我听说你应该总是更喜欢 C++11 中的“按值传递” 因为引入了移动语义。

我很确定您没有从任何有信誉的来源那里听到这个消息。或者实际上是在不恰当地解释实际所说的话。但我没有报价的来源。通常建议的实际上是,如果您要在函数中复制参数,请不要这样做。只需在参数列表中执行(通过值传递)。这将允许您的函数将 r 值参数直接移动到它们的目的地。当你传递左值时,它们会被复制,但无论如何你都会这样做。

【讨论】:

  • 是 Dave Abrahams,这里是 the original,但 Scott Meyer 也说过。您可以通过搜索他的博文标题"Want Speed? Pass by Value." 找到同样著名的专家的几条回复
  • 谢谢,这是一个很有帮助的答案。
  • @BenVoigt:如果 OP 指的是 David Abrahams 的文章,那么正如我所说的,“不恰当地解释了实际所说的内容”
  • 你说得对,Dave 没有说“prefer”,他将其表述为绝对的“准则:不要复制函数参数。相反,按值传递它们并让编译器执行抄袭。”虽然,在上下文中,他显然不是绝对的意思。我认为“喜欢”是一种公平的解释方式。
  • @BenVoigt:问题不在于“偏好”。就是“一直”。 OP 没有提到任何关于您正在复制参数的重要条件。
【解决方案2】:

如果您要进行内部复制,那么按值传递将比一对重载(按右值引用)+(按常量左值引用)多做一个移动构造。

如果 move 构造很便宜,这是少量的运行时开销,以换取更少的编译时间和代码维护开销。

成语是“想要速度?无论如何都要复制?按值传递,而不是通过 const 左值引用。”现实中。

最后,您的基准测试存在缺陷,因为您在回击之前未能保留(足够)。重新分配可能会导致额外的操作。哦,让你的 move 构造函数为 noexcept,因为如果 move 可以在许多情况下抛出,那么符合标准的库会更喜欢一个副本而不是 move。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-25
    • 2016-05-16
    • 1970-01-01
    • 2010-09-24
    • 2011-05-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多