在C ++中通过值传递还是通过常量引用传递更好?
我想知道哪种更好的做法。我意识到通过常量引用传递应该在程序中提供更好的性能,因为您没有在复制变量。
在C ++中通过值传递还是通过常量引用传递更好?
我想知道哪种更好的做法。我意识到通过常量引用传递应该在程序中提供更好的性能,因为您没有在复制变量。
Answers:
它曾经被通常建议的最佳实践1至由常量REF使用通行证的所有类型,除了内建类型(char
,int
,double
等),用于迭代器和用于功能对象(lambda表达式,类从导出std::*_function
)。
在存在移动语义之前,尤其如此。原因很简单:如果按值传递,则必须制作对象的副本,除了很小的对象外,这总是比传递引用更昂贵。
使用C ++ 11,我们获得了move语义。简而言之,移动语义允许在某些情况下可以“按值”传递对象而不复制它。特别是当您传递的对象是rvalue时,就是这种情况。
就其本身而言,移动对象仍然至少与通过引用传递一样昂贵。但是,在许多情况下,函数无论如何都会在内部复制对象-即它将获取参数的所有权。2
在这些情况下,我们需要进行以下(简化)的权衡:
除非对象是右值,否则“按值传递”仍将导致对象被复制。在右值的情况下,可以移动对象,以便第二种情况突然不再是“复制,然后移动”,而是“移动,然后(可能)再次移动”。
对于实施适当的移动的构造(如载体,字符串...)大的物体,第二壳体是则大大大于第一更有效。因此,如果函数拥有参数的所有权,并且对象类型支持有效的移动,则建议使用按值传递。
历史记录:
实际上,任何现代编译器都应该能够弄清楚何时按值传递很昂贵,并在可能的情况下隐式转换调用以使用const ref。
理论上。实际上,编译器不能总是在不破坏函数的二进制接口的情况下进行更改。在某些特殊情况下(当函数内联时),如果编译器可以确定原始对象不会通过函数中的操作更改,则实际上将删除该副本。
但是总的来说,编译器无法确定这一点,而C ++中移动语义的问世使得这种优化的重要性大大降低。
1例如,Scott Meyers,《有效的C ++》。
2对于对象构造函数来说尤其如此,它可以接受参数并将其内部存储为构造对象状态的一部分。
编辑: Dave Abrahams在cpp-next上的新文章:
对于便宜复制的结构,按值传递具有附加优势,即编译器可以假定对象没有别名(不是相同的对象)。使用按引用传递,编译器不能假设总是这样。简单的例子:
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的新值。
有关更多实用的规则,请参见“ 移动构造函数”文章中的一组很好的规则(强烈建议阅读)。
上面的“原始”表示基本上很小的数据类型,这些数据类型只有几个字节长,并且不是多态的(迭代器,函数对象等)或复制昂贵。在那篇论文中,还有另一条规则。这个想法是,有时有人想要复制(以防万一,该参数不能被修改),有时有人不想复制(万一,如果该参数是临时的,则想在函数中使用该参数本身) , 例如)。本文详细说明了如何完成此操作。在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 */
}
但是,如果参数的目的是在参数中写入一些内容,则通过非常量引用将其传递
bool try_parse(T text, my::string &out) {
/* try to parse, write result into out */
}
__restrict__
(也可以在参考文献上使用),而不要使用过多的副本。太糟糕的标准C ++没有采用C99的restrict
关键字。
正如已经指出的,它取决于类型。对于内置数据类型,最好按值传递。甚至一些非常小的结构(例如一对整数)也可以通过传递值来实现更好的性能。
这是一个示例,假设您有一个整数值,并且想要将其传递给另一个例程。如果已将该值优化为存储在寄存器中,则如果要将其传递为引用,则必须首先将其存储在内存中,然后将指向该内存的指针放在堆栈上以执行调用。如果通过值传递它,则只需要将寄存器压入堆栈。(细节要比给定不同的调用系统和CPU时复杂得多)。
如果您正在执行模板编程,通常会因为不知道要传入的类型而被迫始终通过const ref进行传递。对于通过值传递不良值的传递惩罚要比通过内置类型的传递惩罚要差得多。由const ref。
这是我通常在设计非模板函数的接口时通过的工作:
如果函数不想修改参数并且值很容易复制(例如,int,double,float,char,bool等),则按值传递。请注意,std :: string,std :: vector和其余的值标准库中的容器不是)
如果要复制的值非常昂贵并且函数不想修改指向的值并且NULL是函数处理的值,则通过const指针传递。
如果要复制的值非常昂贵并且函数要修改指向的值并且NULL是函数处理的值,则通过非const指针传递。
当值的复制成本很高并且该函数不想修改所引用的值并且如果使用指针代替NULL时,则NULL将不是有效值。
当值复制成本很高且函数要修改所引用的值时,如果要使用指针代替NULL,则NULL将不是有效值。
std::optional
到图片,您不再需要指针。
听起来您已经找到答案了。传递价值很昂贵,但是如果需要,可以给您一个副本。
通常,通过const引用传递更好。但是,如果需要在本地修改函数参数,则最好使用按值传递。对于某些基本类型,按值传递和按引用传递的性能通常相同。实际上由指针内部表示的引用,这就是为什么您可以期望,例如,对于指针而言,两个传递都在性能方面是相同的,或者甚至由于不必要的取消引用而按值传递也可能更快。
按值传递小类型。
通过const引用传递大类型(big的定义在机器之间可能会有所不同),但是在C ++ 11中,如果要使用数据,则按值传递,因为可以利用移动语义。例如:
class Person {
public:
Person(std::string name) : name_(std::move(name)) {}
private:
std::string name_;
};
现在,调用代码将执行以下操作:
Person p(std::string("Albert"));
并且仅创建一个对象并将其直接移到name_
类中的member 中Person
。如果您通过const引用传递,则必须将其放入副本name_
。
简单的区别:-在函数中,我们具有输入和输出参数,因此,如果传递的输入和输出参数相同,则使用按引用调用;否则,如果输入和输出参数不同,则最好使用按值调用。
例 void amount(int account , int deposit , int total )
输入参数:account,存款输出参数:总计
输入和输出是不同的使用方式
void amount(int total , int deposit )
投入总额存款产出总额