提问人:Matt Pascoe 提问时间:11/7/2008 最后编辑:claymationMatt Pascoe 更新时间:6/12/2022 访问量:124415
在 C++ 中,按值传递还是按引用常量传递更好?
Is it better in C++ to pass by value or pass by reference-to-const?
答:
听起来你得到了答案。按值传递很昂贵,但如果需要,可以为您提供一个副本。
评论
取决于类型。您增加了必须进行引用和取消引用的小开销。对于大小等于或小于使用默认复制 ctor 的指针的类型,按值传递可能会更快。
评论
过去通常建议的最佳实践1 对所有类型(内置类型(char
、int
、double
等)使用传递 const ref,但迭代器和函数对象(lambdas,派生自的类)除外。std::*_function
在移动语义出现之前尤其如此。原因很简单:如果按值传递,则必须创建对象的副本,并且除了非常小的对象外,这总是比传递引用更昂贵。
使用 C++11,我们获得了移动语义。简而言之,移动语义允许在某些情况下,可以“按值”传递对象而无需复制它。特别是,当您传递的对象是右值时,就是这种情况。
就其本身而言,移动对象的成本至少与通过引用传递一样昂贵。然而,在许多情况下,函数无论如何都会在内部复制一个对象——即它将获得参数的所有权。阿拉伯数字
在这些情况下,我们进行了以下(简化)权衡:
- 我们可以通过引用传递对象,然后在内部复制。
- 我们可以按值传递对象。
“按值传递”仍会导致对象被复制,除非该对象是右值。在右值的情况下,可以移动对象,因此第二种情况突然不再是“复制,然后移动”,而是“移动,然后(可能)再次移动”。
对于实现正确移动构造函数(如向量、字符串等)的大型对象,第二种情况比第一种情况效率高得多。因此,如果函数获得参数的所有权,并且对象类型支持高效移动,则建议使用按值传递。
历史笔记:
事实上,任何现代编译器都应该能够弄清楚何时按值传递是昂贵的,并在可能的情况下隐式地将调用转换为使用 const ref。
理论上。在实践中,编译器不能总是在不破坏函数的二进制接口的情况下更改这一点。在某些特殊情况下(当函数内联时),如果编译器能够确定原始对象不会通过函数中的操作进行更改,则实际上会省略副本。
但总的来说,编译器无法确定这一点,而 C++ 中移动语义的出现使得这种优化的相关性大大降低。
1 例如,在斯科特·迈耶斯(Scott Meyers)中,有效的C++。
2 对于对象构造函数来说尤其如此,它可能会接受参数并将其存储在内部,以作为构造对象状态的一部分。
评论
std::move
const
通常,通过常量引用传递会更好。 但是,如果您需要在本地修改函数参数,则最好使用传递值。 对于某些基本类型,按值传递和按引用传递的性能通常相同。实际上,引用在内部由指针表示,这就是为什么您可以期望例如,对于指针,两个传递在性能方面是相同的,或者由于不必要的取消引用,甚至通过值传递也会更快。
评论
如前所述,这取决于类型。对于内置数据类型,最好按值传递。即使是一些非常小的结构,例如一对整数,也可以通过传递值来表现更好。
下面是一个示例,假设您有一个整数值,并且您希望将其传递给另一个例程。如果该值已优化为存储在寄存器中,那么如果要将其作为引用传递,则必须首先将其存储在内存中,然后指向堆栈上放置的该内存的指针以执行调用。如果它是按值传递的,则只需要将寄存器推送到堆栈上即可。(细节比给定不同的调用系统和 CPU 要复杂一些)。
如果你正在做模板编程,你通常被迫总是通过 const ref 传递,因为你不知道传入的类型。通过值传递坏东西的惩罚比通过 const ref 传递内置类型的惩罚要糟糕得多。
根据经验,非类类型的值和类的常量引用。 如果一个类真的很小,那么按值传递可能更好,但差异很小。你真正想要避免的是按值传递一些巨大的类,并让它全部复制 - 如果你传递,比如说,一个包含很多元素的 std::vector,这将产生巨大的差异。
评论
std::vector
sizeof(std::vector<int>)
编辑:戴夫·亚伯拉罕斯(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 &),编译器就不可能假设它。因为 G.I 可以被 F->I 别名,并且必须具有 7 的值。因此,编译器必须从内存中重新获取 G.I 的新值。
对于更实用的规则,这里有一组很好的规则,可以在 Move Constructors 一文中找到(强烈推荐阅读)。
- 如果函数打算将参数更改为副作用,请通过非常量引用来获取它。
- 如果函数不修改其参数,并且参数是基元类型,则按值获取它。
- 否则,请按常量引用,但以下情况除外
- 如果函数无论如何都需要复制常量引用,请按值获取。
上面的“原始”基本上是指几个字节长的小数据类型,并且不是多态的(迭代器、函数对象等)或复制成本高昂。在那篇论文中,还有一条规则。这个想法是,有时人们想要复制(以防参数无法修改),有时人们不想要(例如,如果参数无论如何都是临时的,则想在函数中使用参数本身)。本文详细解释了如何做到这一点。在 C++1x 中,该技术可以在语言支持下本地使用。在那之前,我会遵守上述规则。
示例:要使字符串大写并返回大写版本,应始终按值传递:无论如何都必须获取它的副本(不能直接更改常量引用) - 因此最好使其对调用者尽可能透明,并尽早进行复制,以便调用者可以尽可能多地优化 - 如那篇论文中所述:
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__
restrict
小类型的按值传递。
通过大类型的常量引用传递(大的定义可能因机器而异) 但是,在 C++11 中,如果您要使用数据,请传递值,因为您可以利用移动语义。例如:
class Person {
public:
Person(std::string name) : name_(std::move(name)) {}
private:
std::string name_;
};
现在调用代码可以执行以下操作:
Person p(std::string("Albert"));
并且只会创建一个对象并直接移动到类中的成员中。如果通过 const 引用,则必须制作一份副本才能将其放入 .name_
Person
name_
简单的区别:- 在函数中我们有输入和输出参数,所以如果你传递的输入和输出参数相同,那么使用引用调用,否则如果输入和输出参数不同,那么最好使用按值调用。
例void amount(int account , int deposit , int total )
输入参数:账户、存款 输出参数:总计
输入和输出是不同的,使用Vaule调用
void amount(int total , int deposit )
输入存款总额 总产量
这是我在设计非模板函数的界面时通常采用的方法:
如果函数不想修改参数,则按值传递,并且 值的复制成本很低(int、double、float、char、bool 等......请注意,std::string、std::vector 和标准库中的其余容器不是
如果该值的复制成本很高,则通过常量指针传递,而函数确实如此 不想修改指向的值,NULL 是函数处理的值。
如果该值的复制成本很高,则通过非常量指针传递函数 想要修改指向的值,NULL 是函数处理的值。
当复制值的成本很高,并且函数不想修改引用的值时,通过常量引用传递,如果改用指针,则 NULL 将不是有效值。
当值的复制成本很高,并且函数想要修改引用的值时,通过非常量引用传递,如果改用指针,则 NULL 将不是有效值。
评论
std::optional
Pass by referece 比 pass by value 更好。我正在解决 Leetcode 上最长的常见子序列问题。它显示 TLE 的传递值,但接受代码的引用传递。我花了 30 分钟才弄清楚这一点。
上一个:在 C 中按引用或值传递对象#
下一个:在 C++ 中按引用和值传递
评论