在C ++中通过值传递或通过常量引用传递是否更好?

时间:2008-11-06 21:43:01

标签: c++ variables pass-by-reference constants pass-by-value

在C ++中通过值传递或通过常量引用传递是否更好?

我想知道哪个是更好的做法。我意识到通过常量引用传递应该在程序中提供更好的性能,因为你没有复制变量。

11 个答案:

答案 0 :(得分:187)

以前通常建议最佳做法 1 使用const ref传递所有类型,但内置类型(char除外), intdouble等),用于迭代器和函数对象(lambdas,派生自std::*_function的类)。

移动语义存在之前尤其如此。原因很简单:如果你通过值传递,则必须创建对象的副本,除了非常小的对象之外,这总是比传递引用更昂贵。

使用C ++ 11,我们获得了move semantics。简而言之,移动语义允许在某些情况下,可以“按值”传递对象而不复制它。特别是,当您传递的对象是rvalue时,就是这种情况。

本身,移动物体仍然至少与通过参考传递一样昂贵。但是,在许多情况下,函数会在内部复制一个对象 - 即它将占用参数的所有权 2

在这些情况下,我们有以下(简化)权衡:

  1. 我们可以通过引用传递对象,然后在内部复制。
  2. 我们可以按值传递对象。
  3. “按值传递”仍然会导致复制对象,除非该对象是右值。在rvalue的情况下,可以移动对象,这样第二种情况突然不再“复制,然后移动”,而是“移动,然后(可能)再次移动”。

    对于实现正确移动构造函数的大型对象(例如向量,字符串......),第二种情况比第一种情况更有效。因此,如果函数获取参数的所有权,并且对象类型支持有效移动,建议使用pass by value。


    历史记录:

    实际上,任何现代编译器都应该能够确定何时传递值很昂贵,并且如果可能的话隐式转换调用以使用const ref。

    理论上。在实践中,编译器不能总是在不破坏函数的二进制接口的情况下改变它。在某些特殊情况下(当函数被内联时),如果编译器可以通过函数中的操作确定原始对象不会被更改,则实际上将省略该副本。

    但总的来说编译器无法确定这一点,并且C ++中移动语义的出现使得这种优化的相关性降低了。


    1 例如在Scott Meyers, Effective C ++

    2 对于构造函数来说尤其如此,它可以接受参数并将它们存储在内部以构成构造对象状态的一部分。

答案 1 :(得分:93)

编辑: Dave Abrahams关于cpp-next的新文章:

Want speed? Pass by value.


复制便宜的结构的值传递具有额外的优点,即编译器可以假定对象不是别名(不是相同的对象)。使用pass-by-reference,编译器不能总是假设。简单的例子:

foo * f;

void bar(foo g) {
    g.i = 10;
    f->i = 2;
    g.i += 5;
}

编译器可以将其优化为

g.i = 15;
f->i = 2;

因为它知道f和g不共享相同的位置。如果g是引用(foo&),则编译器不能假设。因为g.i然后可能被f-> i别名并且必须具有值7.因此编译器必须从内存中重新获取g.i的新值。

对于更实用的规则,Move Constructors文章(强烈推荐阅读)中提供了一套很好的规则。

  • 如果函数打算将参数更改为副作用,请使用非const引用。
  • 如果函数没有修改其参数且参数是基本类型,则按值取值。
  • 除非在下列情况下,否则请使用const引用
    • 如果函数需要复制const引用,请按值获取。

上面的“原语”基本上是指几个字节长的小数据类型,不是多态的(迭代器,函数对象等......)或复制起来很昂贵。在那篇论文中,还有另一条规则。这个想法是有时人们想要复制(如果参数不能被修改),有时人们不想要(如果有人想在函数中使用参数本身,如果参数是临时的话) , 例如)。该文件详细解释了如何做到这一点。在C ++ 1x中,该技术可以在语言支持下本地使用。在那之前,我会遵守上述规则。

示例:要使字符串大写并返回大写版本,应始终按值传递:无论如何都必须复制它(一个不能直接更改const引用) - 所以最好使其变为透明尽可能让调用者尽早复制,以便调用者尽可能地进行优化 - 详见该文章:

my::string uppercase(my::string s) { /* change s and return it */ }

但是,如果您不需要更改参数,请参考const:

bool all_uppercase(my::string const& s) { 
    /* check to see whether any character is uppercase */
}

但是,如果参数的目的是在参数中写入内容,则通过非const引用传递它

bool try_parse(T text, my::string &out) {
    /* try to parse, write result into out */
}

答案 2 :(得分:12)

取决于类型。您正在添加必须进行引用和取消引用的小额开销。对于大小等于或小于使用默认副本ctor的指针的类型,传递值可能会更快。

答案 3 :(得分:8)

正如已经指出的那样,它取决于类型。对于内置数据类型,最好按值传递。即使是一些非常小的结构,例如一对整数,也可以通过传递值来表现得更好。

这是一个示例,假设您有一个整数值,并且您希望将其传递给另一个例程。如果该值已被优化以存储在寄存器中,那么如果要将其作为引用传递,则首先必须将其存储在内存中,然后将指向该内存的指针放在堆栈中以执行调用。如果它是通过值传递的,那么所需要的只是将寄存器压入堆栈。 (细节比给定不同的呼叫系统和CPU更复杂。)

如果你正在进行模板编程,你通常被迫总是通过const ref,因为你不知道传入的类型。传递一些不好的值传递惩罚比传递一个内置的惩罚更糟糕-in由const ref。

输入

答案 4 :(得分:5)

听起来你得到了答案。通过价值传递是昂贵的,但如果您需要,可以给您一份副本。

答案 5 :(得分:5)

这是我在设计非模板函数的界面时通常所做的工作:

  1. 如果函数不想修改参数,则传递值 复制的值很便宜(int,double,float,char,bool等...请注意,标准库中的std :: string,std :: vector和其余容器都不是)

    < / LI>
  2. 如果要复制的值很昂贵,那么传递const指针 不希望修改指向的值,NULL是函数处理的值。

  3. 如果复制值和函数的值很高,则通过非const指针传递 想要修改指向的值,NULL是函数处理的值。

  4. 当复制值很昂贵且函数不想修改引用的值时,通过const引用传递,如果使用指针,则NULL将不是有效值。

  5. 当复制值很昂贵时,通过非const引用传递,并且函数想要修改引用的值,如果使用指针,则NULL将不是有效值。

答案 6 :(得分:4)

通过const引用的规则更好。 但是如果你需要在本地修改函数参数,最好使用值传递。 对于某些基本类型,性能通常与传递值和引用相同。实际上是指针在内部由指针表示,这就是为什么你可以期望,对于指针来说两个传递在性能方面是相同的,或者甚至通过值传递可以更快,因为不必要的解引用。

答案 7 :(得分:1)

根据经验,非类类型的值和类的const引用。 如果一个类真的很小,那么传递值可能会更好,但差别很小。你真正想要避免的是通过价值传递一些巨大的类并将它全部重复 - 如果你传递一个std :: vector并且其中含有相当多的元素,这将产生巨大的差异。

答案 8 :(得分:1)

传递小类型的值。

传递大型类型的const引用(大型的定义可能因机器而异)但是,在C ++ 11中,如果要使用数据,则传递值,因为您可以利用移动语义。例如:

class Person {
 public:
  Person(std::string name) : name_(std::move(name)) {}
 private:
  std::string name_;
};

现在调用代码会执行:

Person p(std::string("Albert"));

只会创建一个对象并将其直接移动到类name_中的成员Person中。如果您通过const引用,则必须创建一个副本以将其放入name_

答案 9 :(得分:-3)

通过引用传递比通过值传递更好。我正在解决 Leetcode 上的最长公共子序列问题。它显示了按值传递的 TLE,但接受了按引用传递的代码。我花了 30 分钟才弄明白。

答案 10 :(得分:-5)

简单的区别: - 在函数中我们有输入和输出参数,所以如果你传递的输入和输出参数是相同的,那么如果输入和输出参数不同则使用引用调用,那么最好使用按值调用。

示例void amount(int account , int deposit , int total )

输入参数:账户,存款 输出参数:总计

输入和输出是不同的使用vaule呼叫

  1. void amount(int total , int deposit )
  2. 输入总存款 输出总数