按值传递的速度要快得多吗?

Is pass by value that much faster?

提问人:user4608393 提问时间:2/26/2015 更新时间:2/26/2015 访问量:284

问:

我听说你应该总是更喜欢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_valuepass_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

如您所见,非函数总共需要 2 个复制构造和 1 个移动构造。该函数总共需要 3 个复制构造和 2 个移动构造。看起来,不出所料,无论如何,对象都会被复制,那么为什么大家都说按值传递呢?pass_by_valuepass_by_value

C++ C++11 移动语义传递

评论

0赞 T.C. 2/26/2015
测试线束已损坏。你的一些触发重新分配,而另一些则不会。按值传递不是一对重载的性能提升;这是可维护性的提升。push_back
0赞 Praetorian 2/26/2015
正如 T.C. 所说,请确保您的足够大以防止重新分配 - coliru.stacked-crooked.com/a/e424a3073bdc727c 另外,这是您的示例的另一个版本,它会导致重新分配,但在发生这种情况时避免了复制结构,注意到区别了吗?(我做了 的移动构造函数vectorMyClassnoexcept)
0赞 Ben Voigt 2/26/2015
大多是 stackoverflow.com/q/21605579/103167 的重复
1赞 M.M 2/26/2015
_Typename因为变量的名称是一个非常可怕的命名约定......
2赞 T.C. 2/26/2015
@MattMcNabb 更不用说它使用保留标识符了......

答:

2赞 Benjamin Lindley 2/26/2015 #1

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

二是具有较强的异常安全保障。因此,如果你的移动构造函数不是 noexcept,它就不会使用它(除非该类是不可复制的)。它将改用 copy 构造函数。std::vector::push_back

第三

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

我很确定你没有从任何有信誉的来源听到这个消息。或者实际上不恰当地转述了实际所说的内容。但我没有报价的来源。通常的建议是,如果你无论如何都要在函数中复制你的参数,就不要这样做。只需在参数列表中执行此操作(通过按值传递)。这将允许您的函数将 r 值参数直接移动到其目的地。当你传递 l 值时,它们将被复制,但你还是要这样做。

评论

0赞 Ben Voigt 2/26/2015
这是戴夫·亚伯拉罕斯(Dave Abrahams),这是曾经)原版,但斯科特·迈耶(Scott Meyer)也说过。你可以通过搜索他的博客文章的标题,找到同样著名的专家的几个回复,“想要速度吗?按价值传递。
1赞 Benjamin Lindley 2/26/2015
@BenVoigt:如果大卫·亚伯拉罕斯(David Abrahams)的文章是OP所指的那篇文章,那么正如我所说,它就是“不恰当地转述了实际所说的内容”。
0赞 Ben Voigt 2/26/2015
你是对的,Dave 没有说“首选”,他把它写成一个绝对的“指导方针:不要复制你的函数参数。相反,按值传递它们,让编译器进行复制。虽然,在上下文中,他显然不是绝对的意思。我认为“首选”是一种公平的释义方式。
1赞 Benjamin Lindley 2/26/2015
@BenVoigt:问题不在于“偏好”。这是“总是”。OP 没有提到任何关于您无论如何都在复制参数的重要条件。
0赞 Ben Voigt 2/26/2015
你可以在你的回答中明确这一点。以及最近的智慧是即使在那时也不这样做。
0赞 Yakk - Adam Nevraumont 2/26/2015 #2

如果要进行内部复制,则按值传递将比重载对(按右值传递 ref)+(按常量左值 ref 传递)多执行一次移动构造。

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

成语是“想要速度吗?无论如何制作副本?按值传递,而不是按常量左值引用传递。

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