通过值或引用传递给需要存储副本的C ++构造函数?

时间:2009-12-04 15:52:22

标签: c++ optimization constructor pass-by-reference pass-by-value

C ++(隐式或显式)值构造函数是否应该通过value或reference-to-const接受其参数,当它需要以任何方式在其对象中存储参数的副本时?

这是我能想到的最短的例子:

struct foo {
    bar _b;
    foo(bar [const&] b) // pass by value or reference-to-const?
        : _b(b) { }
};

这里的想法是,我希望在创建foo对象时,以创建foo对象的各种方式最小化对bar的复制构造函数的调用。

请注意我对copy elision和(Named)返回值优化有一点了解,并且我已阅读"Want Speed? Pass by Value",但我不认为该文章直接针对此用例。

编辑:我应该更具体。

假设我无法知道sizeof(bar),或者bar是否是基本的内置类型(bar可能是模板参数,{{1可能是类模板而不是类)。另外,不要假设foo的构造函数可以内联(或foo),就此而言。假设我至少可能使用实现RVO的编译器。

我希望有一种可能性(给定编译器优化)这样的调用不会调用bar的复制构造函数(即使在bar中执行_b(b) {1}}的初始化列表):

foo

是否有可能(根据C ++ 98标准)可以做到这一点,如果是这样,如果foo f = function_that_creates_and_returns_a_bar_object_using_rvo(); 通过引用到const接受其参数,它或多或少有可能工作按值计算?

8 个答案:

答案 0 :(得分:5)

一切都是平等的,我通过const& amp;对于足够复杂的类,以及POD和简单对象的值。

规定优点/缺点是通过const引用而不是传统的值传递

肯定:

  • 避免复制(对于使用昂贵副本的对象而言,这是一个很大的优势)
  • 只读访问

否定:

  • 如果他们真的想
  • ,有人可以将const从引用中删除

更重要的是,积极的是你在复制发生时明确控制(在初始化列表中初始化_b的情况下)。思考负面因素......我同意这是一种风险。我认为几乎所有优秀的程序员都会因为使用const_cast而感到肮脏。此外,你可以尽职尽责地搜索const_cast并将西红柿扔给那些从const中抛出const的人。但嘿,你永远不知道,谁有时间像鹰一样看代码?)?

我的主观意见是,在足够复杂的类和性能重要的环境中,避免复制构造函数的好处超过了风险。然而,对于非常愚蠢和POD类,我倾向于复制数据并按值传递。

答案 1 :(得分:4)

看看question

const T & arg使用sizeof(T)>sizeof(void*)T arg使用sizeof(T) <= sizeof(void*)。所有基本类型都应该是此规则的例外

答案 2 :(得分:2)

在风格上,我会说通过引用传递是更好的方法。

如果表现真的很重要,那就不要猜。测量它。

答案 3 :(得分:2)

我没有检查标准所说的内容,但通过经验尝试,我可以告诉你GCC不会优化副本,无论构造函数是通过值还是通过const引用接受参数。

但是,如果构造函数采用const引用,则当您从现有的 bar 对象创建 foo 时,它会设法避免不必要的副本。

总结:

Bar b = makeBar();         // No copy, because of RVO
FooByValue f1 = makeBar(); // Copy constructor of Bar called once
FooByRef f2 = makeBar();   // Copy constructor of Bar called once
FooByValue f3(b);          // Copy constructor of Bar called twice
FooByRef f4(b);            // Copy constructor of Bar called only once

不是说我是编译专家,但我认为通常不能将RVO返回到任意位置(例如 foo 对象的成员字段)。相反,它需要目标位于堆栈的顶部。

答案 4 :(得分:2)

在C ++ 98和C ++ 03中,您应该传递const& bar然后复制。在C ++ 0x中,您应该传递bar然后进行移动(前提是bar有一个移动构造函数)。

#include <utility>

struct foo
{
    bar _b;

    foo(bar b) : _b(std::move(b)) {}
};

如果使用lvalue参数构造foo,则将调用复制构造函数以创建副本b,并将该副本移动到_b。如果使用rvalue参数构造foo,则会调用bar的移动构造函数移动到b,然后它将再次移动到_b

答案 5 :(得分:1)

我假设bar有一个bar(const bar &b)形式的普通复制构造函数

在此处获取const引用。然后bar的构造函数将获取该引用,并执行复制。总份数:1。

如果你关闭了引用,编译器会复制b,将副本传递给foo的构造函数,然后将其传递给bar并复制它。

答案 6 :(得分:0)

老实说,对于这种情况,最好的选择是使你的构造函数inline,只要bar的构造函数不抛出。然后通过引用传递。

此外,正如您所怀疑的那样,您的文章不适用于此。

答案 7 :(得分:-1)

将一个shared_pointer保存到您班级的栏中并按原样传递。这样你永远不会调用复制构造函数:)。