用C ++传递值还是传递常量引用更好?
用C ++传递值还是传递常量引用更好?
我想知道哪种更好的做法。 我意识到,通过不断的引用应该提供更好的性能,因为你没有做一个variables的副本。
它通常被推荐为最佳实践1 ,除了内buildtypes( char
, int
, double
等)外,对于迭代器和函数对象 (lambdas,派生自std::*_function
)。
在移动语义存在之前尤其如此。 原因很简单:如果按值传递,则必须创build对象的副本,除了非常小的对象之外,这通常比传递引用更昂贵。
用C ++ 11,我们获得了移动语义 。 简言之,移动语义允许在某些情况下,一个对象可以被“按价值”传递而不需要复制。 特别是当你传递的对象是右值的时候就是这种情况。
移动物体本身至less与通过参考一样昂贵。 然而,在许多情况下,函数会在内部复制一个对象 – 也就是说它将拥有参数的所有权 。 2
在这些情况下,我们有以下(简化)权衡:
- 我们可以通过引用传递对象,然后在内部复制。
- 我们可以通过价值传递对象。
“按值传递”仍然会导致对象被复制,除非对象是右值。 在右值的情况下,可以移动对象,以便第二种情况突然不再是“复制,然后移动”,而是“移动,然后(可能)再次移动”。
对于实现合适的移动构造函数(比如向量,string…)的大对象,第二种情况比第一种效率高得多。 因此, 如果函数拥有参数的所有权,并且对象types支持高效移动 ,则build议使用值传递 。
历史笔记:
实际上,任何一个现代编译器都应该能够计算出传递值是昂贵的,并且如果可能的话,隐式地将调用转换为使用const ref。
理论上。 在实践中,编译器不能总是在不破坏函数的二进制接口的情况下改变它。 在一些特殊情况下(当函数被内联时),如果编译器能够通过函数中的操作来确定原始对象不会被改变,那么复制实际上将被消除。
但是一般情况下,编译器不能确定这一点,而C ++中移动语义的出现使得这种优化更加不那么重要。
1例如Scott Meyers, Effective C ++ 。
2这对于对象构造函数来说尤其如此,这些构造函数可能需要参数并将其内部存储为构造对象状态的一部分。
编辑:新的文章由戴夫亚伯拉罕cpp-next:
想要速度? 按价值传递。
通过价值的复制是廉价的结构具有额外的好处,编译器可能会认为对象不别名(不是相同的对象)。 使用传递引用编译器不能总是假设。 简单的例子:
foo * f; void bar(foo g) { gi = 10; f->i = 2; gi += 5; }
编译器可以优化它
gi = 15; f->i = 2;
因为它知道f和g不共享相同的位置。 如果g是一个引用(foo&),那么编译器就不能假设这一点。 因为gi可以被f-> i替代,并且必须具有7的值,所以编译器将不得不从内存中重新获取gi的新值。
对于更多的实践规则,这里有一个很好的移动构造函数的文章(强烈推荐阅读)。
- 如果函数试图改变参数作为一个副作用,通过非const引用。
- 如果函数没有修改它的参数,并且参数是基本types的话,那就把它作为值。
- 除非在以下情况下,否则请使用const引用
- 如果该函数无论如何都需要复制const引用,那就把它作为值。
上面的“Primitive”意味着基本上只有几个字节长的小数据types,并且不是多态的(迭代器,函数对象等等)或者复制的昂贵的。 在那篇文章中,还有另外一个规则。 这个想法是,有时候人们想要复制(如果参数不能被修改),有时候不想要(如果参数本身是临时的, 例如)。 本文详细解释了如何做到这一点。 在C ++ 1x中,该技术可以与语言支持一起使用。 在此之前,我会遵循上述规定。
例子:为了使string大写,并返回大写版本,应该总是按值传递:无论如何,一个必须要拷贝它(不能直接改变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 */ }
取决于types。 你正在添加一个小的开销,不得不做一个引用和取消引用。 对于大小等于或小于使用默认拷贝的指针的types,传递值可能会更快。
正如已经指出的那样,这取决于types。 对于内置数据types,最好按值传递。 即使是一些非常小的结构,例如一对整数,也可以通过传递值得到更好的performance。
这里是一个例子,假设你有一个整数值,你想传递给另一个例程。 如果该值已被优化存储在一个寄存器中,那么如果你想传递它作为引用,它首先必须被存储在存储器中,然后是一个指向堆栈上的内存的指针来执行调用。 如果按值传递,所需要的只是将该寄存器推入堆栈。 (细节比给定不同的呼叫系统和CPU要复杂一些)。
如果你正在做模板编程,你通常被迫总是通过const ref来传递,因为你不知道传入的types。通过惩罚来传递错误的值比通过内置types通过const ref。
听起来像你有你的答案。 按价值传递价格昂贵,但如果您需要,可以提供一份副本供您使用。
作为通过const引用的规则更好。 但是如果你需要在本地修改你的函数参数,你应该更好地使用按值传递。 对于一些基本的types来说,一般的performance都是一样的,都是通过价值传递和引用。 其实引用在内部由指针表示,这就是为什么你可以期望,例如对于指针来说,这两个传递是相同的性能方面,甚至传递值可以更快,因为不必要的解除引用。
这是我devise非模板函数的接口时通常的工作:
-
如果函数不想修改参数并且值很便宜(int,double,float,char,bool等等),那么按值传递…注意std :: string,std :: vector和其他标准库中的容器不是)
-
如果值复制的代价很高,并且函数不希望修改指向的值,并且NULL是函数处理的值,则由const指针传递。
-
如果值非常昂贵,那么通过非const指针进行复制,并且该函数要修改指向的值,并且NULL是该函数处理的值。
-
当通过const引用传递值时,要复制的代价很大,并且函数不希望修改引用的值,如果使用指针,则NULL不会是有效的值。
-
当值非常昂贵时,通过非const引用传递,并且函数想要修改引用的值,如果使用指针,则NULL不会是有效的值。
作为一个经验法则,非类types的值和类的const引用。 如果一个class级真的很小,那么通过价值传递可能会更好,但差距很小。 你真正想避免的是通过价值传递一些巨大的类,并重复这一切 – 如果你传递了一个std :: vector,其中包含了很多元素,这将会产生巨大的影响。
按小值传递。
传递大types的const引用(big的定义可以在不同的机器之间变化),但是在C ++ 11中,如果要使用数据传递值,因为可以利用移动语义。 例如:
class Person { public: Person(std::string name) : name_(std::move(name)) {} private: std::string name_; };
现在调用代码会这样做:
Person p(std::string("Albert"));
并且只有一个对象会被创build,并直接移动到Person
类的成员name_
中。 如果你通过const引用传递,必须将其复制到name_
。
简单的区别: – 在函数中我们有input和输出参数,所以如果你的传入input和输出参数是相同的,那么如果input和输出参数不同,则使用引用调用else,那么最好使用按值调用。
例子void amount(int account , int deposit , int total )
input参数:账户,存款输出参数:总计
input和输出是不同的使用方式
-
void amount(int total , int deposit )
input总存款总量