在C++中通过值传递或通过常量引用传递是否更好?
我想知道哪个是更好的做法.我意识到通过常量引用传递应该在程序中提供更好的性能,因为你没有复制变量.
它曾经被通常建议的最佳做法1至使用由常量REF通行证的所有类型,除了内建类型(char
,int
,double
等),用于迭代器和用于功能对象(lambda表达式,类从导出std::*_function
).
在移动语义存在之前尤其如此.原因很简单:如果你通过值传递,则必须创建对象的副本,除了非常小的对象之外,这总是比传递引用更昂贵.
使用C++ 11,我们获得了移动语义.简而言之,移动语义允许在某些情况下,可以"按值"传递对象而不复制它.特别是,当您传递的对象是右值时就是这种情况.
就其本身而言,移动物体仍然至少与通过参考传递一样昂贵.但是,在许多情况下,函数无论如何都会在内部复制一个对象 - 即它将取得参数的所有权.2
在这些情况下,我们有以下(简化)权衡:
我们可以通过引用传递对象,然后在内部复制.
我们可以按值传递对象.
除非对象是右值,否则"按值传递"仍会导致复制对象.在rvalue的情况下,可以移动对象,使第二种情况突然不再"复制,然后移动",而是"移动,然后(可能)再次移动".
对于实施适当的移动的构造(如载体,字符串...)大的物体,第二壳体是则大大大于第一更有效.因此,如果函数获取参数的所有权,并且对象类型支持高效移动,则建议使用pass by value.
历史记录:
实际上,任何现代编译器都应该能够确定何时传递值很昂贵,并且如果可能的话隐式转换调用以使用const ref.
理论上.实际上,编译器不能总是在不破坏函数的二进制接口的情况下改变它.在某些特殊情况下(当函数内联时),如果编译器可以确定原始对象不会通过函数中的操作进行更改,则实际上将省略该副本.
但总的来说编译器无法确定这一点,并且C++中移动语义的出现使得这种优化的相关性降低了.
1例如Scott Meyers,Effective C++.
2对于对象构造函数尤其如此,对象构造函数可以接受参数并将其存储在内部以构成构造对象的状态.
编辑: 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&),则编译器不能假设.因为gi可能会被f-> i别名并且必须具有值7.因此编译器必须从内存中重新获取gi的新值.
对于更实用的规则,这里有一组很好的规则,可以在Move Constructors文章中找到(强烈推荐阅读).
如果函数打算将参数更改为副作用,请使用非const引用.
如果函数不修改其参数且参数是基本类型,则按值取值.
否则在const引用中使用它,除了以下情况
如果函数无论如何都需要复制const引用,请按值取值.
"Primitive" above means basically small data types that are a few bytes long and aren't polymorphic (iterators, function objects, etc...) or expensive to copy. In that paper, there is one other rule. The idea is that sometimes one wants to make a copy (in case the argument can't be modified), and sometimes one doesn't want (in case one wants to use the argument itself in the function if the argument was a temporary anyway, for example). The paper explains in detail how that can be done. In C++1x that technique can be used natively with language support. Until then, i would go with the above rules.
示例:要使字符串大写并返回大写版本,应始终按值传递:无论如何都必须复制它(一个不能直接更改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 */ }
取决于类型.您正在添加必须进行引用和取消引用的小额开销.对于大小等于或小于使用默认复制ctor的指针的类型,传递值可能会更快.
正如已经指出的那样,它取决于类型.对于内置数据类型,最好按值传递.即使是一些非常小的结构,例如一对整数,也可以通过传递值来表现得更好.
下面是一个示例,假设您有一个整数值,并且您希望将其传递给另一个例程.如果该值已被优化以存储在寄存器中,那么如果要将其作为引用传递,则首先必须将其存储在内存中,然后将指向该内存的指针放在堆栈中以执行调用.如果它是通过值传递的,那么所需要的只是将寄存器压入堆栈.(细节比给定不同的呼叫系统和CPU更复杂一些).
如果你正在进行模板编程,你通常不得不总是通过const ref,因为你不知道传入的类型.传递一些不好的值的传递惩罚要比传递一个内置类型的惩罚更糟糕.通过const ref.
这是我在设计非模板函数的界面时通常所做的工作:
如果函数不想修改参数并且值复制便宜(int,double,float,char,bool等等),则按值传递...注意std :: string,std :: vector,其余的标准库中的容器是NOT)
如果复制值很昂贵,并且函数不想修改指向的值,则传递const指针,NULL是函数处理的值.
如果复制值很昂贵,并且函数想要修改指向的值,则传递非const指针,NULL是函数处理的值.
当复制值很昂贵时,传递const引用,并且函数不想修改引用的值,如果使用指针,NULL将不是有效值.
当复制值很昂贵时,通过非const引用传递,并且函数想要修改引用的值,如果使用指针,则NULL将不是有效值.
听起来你得到了答案.通过价值传递是昂贵的,但如果您需要,可以给您一份副本.