是通过价值更快?

时间:2015-02-26 02:27:01

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

我听说你应该总是喜欢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个移动构造。看起来,正如预期的那样,对象无论如何都会被复制,那么为什么每个人都说通过值传递?

2 个答案:

答案 0 :(得分:2)

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

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

第三,

  

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

我很确定你没有听到任何声誉良好的消息来源。或实际上是不恰当地解释实际所说的内容。但我没有引用的来源。通常建议的是,如果你打算在你的函数中复制你的参数,不要。只需在参数列表中执行(通过按值传递)。这将允许您的函数将r值参数直接移动到其目标。当你传递l值时,它们将被复制,但无论如何你都会这样做。

答案 1 :(得分:0)

如果你打算制作一个内部副本,那么按值传递将完成一个移动构造,而不是一对重载(通过rvalue ref)+(通过const lvalue ref传递)。

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

成语是“想要速度?无论如何制作副本?通过值传递,而不是通过const lvalue reference。”实际上。

最后,您的基准测试存在缺陷,因为您在推迟之前未能保留(足够)。重新分配可能会导致额外的操作。哦,并使你的移动构造函数noexcept,因为如果移动可以在很多情况下抛出,那么符合要求的库会更喜欢移动副本。