在C ++中通过值传递或通过常量引用传递是否更好?
我想知道哪个是更好的做法。我意识到通过常量引用传递应该在程序中提供更好的性能,因为你没有复制变量。
答案 0 :(得分:187)
以前通常建议最佳做法 1 使用const ref传递所有类型,但内置类型(char
除外), int
,double
等),用于迭代器和函数对象(lambdas,派生自std::*_function
的类)。
在移动语义存在之前尤其如此。原因很简单:如果你通过值传递,则必须创建对象的副本,除了非常小的对象之外,这总是比传递引用更昂贵。
使用C ++ 11,我们获得了move semantics。简而言之,移动语义允许在某些情况下,可以“按值”传递对象而不复制它。特别是,当您传递的对象是rvalue时,就是这种情况。
本身,移动物体仍然至少与通过参考传递一样昂贵。但是,在许多情况下,函数会在内部复制一个对象 - 即它将占用参数的所有权。 2
在这些情况下,我们有以下(简化)权衡:
“按值传递”仍然会导致复制对象,除非该对象是右值。在rvalue的情况下,可以移动对象,这样第二种情况突然不再“复制,然后移动”,而是“移动,然后(可能)再次移动”。
对于实现正确移动构造函数的大型对象(例如向量,字符串......),第二种情况比第一种情况更有效。因此,如果函数获取参数的所有权,并且对象类型支持有效移动,建议使用pass by value。
历史记录:
实际上,任何现代编译器都应该能够确定何时传递值很昂贵,并且如果可能的话隐式转换调用以使用const ref。
理论上。在实践中,编译器不能总是在不破坏函数的二进制接口的情况下改变它。在某些特殊情况下(当函数被内联时),如果编译器可以通过函数中的操作确定原始对象不会被更改,则实际上将省略该副本。
但总的来说编译器无法确定这一点,并且C ++中移动语义的出现使得这种优化的相关性降低了。
1 例如在Scott Meyers, Effective C ++ 。
2 对于构造函数来说尤其如此,它可以接受参数并将它们存储在内部以构成构造对象状态的一部分。
答案 1 :(得分:93)
编辑: Dave Abrahams关于cpp-next的新文章:
复制便宜的结构的值传递具有额外的优点,即编译器可以假定对象不是别名(不是相同的对象)。使用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文章(强烈推荐阅读)中提供了一套很好的规则。
上面的“原语”基本上是指几个字节长的小数据类型,不是多态的(迭代器,函数对象等......)或复制起来很昂贵。在那篇论文中,还有另一条规则。这个想法是有时人们想要复制(如果参数不能被修改),有时人们不想要(如果有人想在函数中使用参数本身,如果参数是临时的话) , 例如)。该文件详细解释了如何做到这一点。在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)
这是我在设计非模板函数的界面时通常所做的工作:
如果函数不想修改参数,则传递值 复制的值很便宜(int,double,float,char,bool等...请注意,标准库中的std :: string,std :: vector和其余容器都不是)
< / LI>如果要复制的值很昂贵,那么传递const指针 不希望修改指向的值,NULL是函数处理的值。
如果复制值和函数的值很高,则通过非const指针传递 想要修改指向的值,NULL是函数处理的值。
当复制值很昂贵且函数不想修改引用的值时,通过const引用传递,如果使用指针,则NULL将不是有效值。
当复制值很昂贵时,通过非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呼叫
void amount(int total , int deposit )
输入总存款 输出总数