C++:如何决定是按引用还是按值传递参数
C++: How do I decide if to pass params by ref or by value?
>有了C++我如何决定我应该按值还是按引用/指针传递参数?(告诉我 32 位和 64 位的答案(让我们以 A 为例。作为指向 32 位值的指针,2 个 32 位值是小于还是相等?
对我来说,B 似乎我总是应该按价值传递。C 我认为我应该按值传递,但有人告诉我(但我还没有看到证据(处理器不处理值而不是它们的位大小,所以工作量更大。因此,如果我传递它们,按值传递会不会有更多的工作,因此 byref 更快?最后我扔了一个枚举。我认为枚举应该始终按值
注意:当我说 ref 时,我的意思是常量引用或指针(不能忘记常量......
struct A { int a, b; }
struct B { int a; }
struct C { char a, b; }
enum D { a,b,c }
void fn(T a);
现在告诉我答案,如果我多次推送参数并且代码不使用尾部调用?(假设这些值直到 4 次左右的调用深度才被使用(
忘记堆栈大小。如果要更改引用,则应按引用传递,否则应按值传递。
防止通过允许函数意外更改数据而引入的错误类型远比浪费几个字节的堆栈空间重要得多。
如果堆栈空间成为问题,请停止使用如此多的级别(例如用迭代解决方案替换递归解决方案(或扩展堆栈。四个级别的递归通常不会那么繁重,除非你的结构很大,或者你在嵌入式世界中运行。
如果性能成为问题,请找到更快的算法:-(如果这是不可能的,那么您可以查看按引用传递,但您需要了解它违反了调用方和被调用方之间的合同。如果你能忍受,那没关系。我一般不能:-(
值/引用二分法的目的是控制在语言级别作为参数传递的东西会发生什么,而不是摆弄语言实现的工作方式。
为了保持一致性,我通过引用传递所有参数,包括内置参数(当然,在可能的情况下使用const
(。
我确实在性能关键领域对此进行了测试——与内置相比,最坏的情况损失是微不足道的。对于非内置函数,以及当调用很深时(作为泛化(,引用可以快得多。这对我来说很重要,因为我正在做相当多的深度TMP,其中功能体很小。
如果您正在计算指令数,硬件缺乏寄存器(例如嵌入式(,或者如果函数不是内联的良好候选者,则可以考虑打破该约定。
不幸的是,您提出的问题比看起来更复杂 - 答案可能因您的平台,ABI,呼叫约定,寄存器计数等而有很大差异。
取决于您的要求,但最佳做法是通过引用传递,因为它会减少内存占用空间。
如果按值传递大型对象,则会在内存中创建该对象的副本,并调用复制构造函数来复制此对象的副本。
因此,这将需要更多的机器周期,而且,如果按值传递,更改不会反映在原始对象中。
因此,请尝试通过引用传递它们。
希望这对您有所帮助。
问候,肯
首先,引用和指针是不一样的。
通过指针传递
通过指针传递参数(如果其中任何/其中一些适用(:
- 传递的元素可以为 null。 资源
- 在被调用函数内部分配,调用方负责释放此类资源。请记住,在这种情况下,要为该资源提供一个 free(( 函数。
- 该值属于变量类型,例如
void*
。当它的类型在运行时确定时或取决于使用模式(或隐藏实现 - 即 Win32 HANDLE(,例如线程过程参数。(这里支持c++模板和std::function,并且仅在您的环境不允许的情况下才使用指针来实现此目的。
按引用传递
通过引用传递参数(如果其中任何/其中一些适用(:
- 大多数时候。(首选通过常量引用传递(
- 如果希望对传递的参数的修改对调用方可见。(除非使用常量引用(。
- 如果传递的参数从不为 null。
- 如果您知道传递的参数类型是什么,并且您可以控制函数的签名。
通过副本传递
如果存在以下情况/某些适用,请传递副本:
- 通常尽量避免这种情况。
- 如果要对传递的参数的副本进行操作,即您知道被调用的函数无论如何都会创建一个副本。
- 使用小于系统指针大小的基元类型 - 因为与常量引用相比,它没有性能/内存差异。
- 这很棘手 - 当您知道该类型实现移动构造函数时(例如 C++11 中的 std::string(。然后看起来好像你正在通过副本。
这三个清单中的任何一个都可以更长,但我想说,这些是基本的经验法则。
您的完整问题对我来说有点不清楚,但是我可以回答您何时使用按值传递或按引用传递。
按值传递时,将参数的完整副本复制到调用堆栈中。这就像你在函数调用中创建一个局部变量,用你传入的任何内容进行初始化。
通过引用时,您...好吧,通过参考。主要区别在于您可以修改外部对象。
对于通过引用传递的大型对象,这样做的好处是可以减少内存负载。对于基本数据类型(例如 32 位或 64 位整数(,性能可以忽略不计。
一般来说,如果你打算在C/C++中工作,你应该学会使用指针。将对象作为参数传递几乎总是通过指针(相对于引用(传递。您绝对必须使用引用的少数实例是在复制构造函数中。您还需要在运算符中使用它,但这不是必需的。
按值复制对象通常是一个坏主意 - 更多的CPU来执行构造函数;更多的内存用于实际对象。使用 const
防止函数修改对象。函数签名应告诉调用方引用的对象可能发生的情况。
像int
、char
、pointers
通常按值传递。
至于你概述的结构,按价值传递并不重要。您需要进行分析才能找到答案,但是在程序的宏伟计划中,您最好在其他地方寻找以提高CPU和/或内存方面的性能。
担心优化之前,我会考虑你是想要值还是引用语义。通常,如果您希望要调用的方法能够修改参数,则通过引用传递。在这种情况下,您可以像在 C 中一样传递指针,但惯用C++倾向于使用引用。
没有规则说小型类型或枚举应始终按值传递。有很多代码传递int&
参数,因为它们依赖于通过引用传递的语义。此外,您应该记住,对于任何相对较小的数据类型,您不会注意到按引用传递和按值传递之间的速度差异。
也就是说,如果你有一个非常大的结构,你可能不想做很多副本。这是const
引用很方便的地方。请记住,尽管C++中的const
并未严格执行(即使它被认为是不好的做法,您也可以始终将其const_cast
(。没有理由通过int
传递const int&
,尽管有理由通过ClassWithManyMembers
传递const ClassWithManyMembers&
。
如果您打算将它们视为值,我会说您列出的所有结构都可以按值传递。考虑到如果你调用一个接受一个 struct Rectangle{int x, y, w, h}
类型参数的函数,这与独立传递这 4 个参数是一样的,这真的没什么大不了的。通常,您应该更担心复制构造函数必须执行的工作 - 例如,按值传递vector
可能不是一个好主意,因为它必须动态分配内存并遍历您不知道其大小的列表,并调用更多的复制构造函数。
虽然你应该记住所有这些,但一个好的一般规则是:如果你想要引用语义,请通过重新围栏。否则,按值传递内部函数,通过常量引用传递其他内容。
此外,C++11 引入了 r 值引用,这使事情进一步复杂化。但这是一个不同的话题。
这些是我使用的规则:
- 对于本机类型:
- 当它们是输入参数时按值
- 当它们是必需的输出参数时,通过非常量引用
- 对于结构或类:
- 当它们是输入参数时,通过常量引用
- 当它们是输出参数时,通过非常量引用
- 对于数组:
- 当它们是输入参数时,通过 const 指针(const 适用于数据,而不是这里的指针,即
const TYPE *
( - 当它们是输出参数时通过指针(const 适用于数据,而不是指针(
- 当它们是输入参数时,通过 const 指针(const 适用于数据,而不是这里的指针,即
我发现很少有时候需要对上述规则进行例外处理。想到的一个例外是可选的结构或类参数,在这种情况下,引用将不起作用。在这种情况下,我使用常量指针(输入(或非常量指针(输出(,以便您也可以传递 0。
如果你想要一个副本,那么按值传递。如果要更改它,并且希望在函数外部看到这些更改,请通过引用传递。如果您想要速度并且不想更改它,请通过 const 引用传递。
- 将const引用参数初始化为默认参数会导致悬空引用吗
- 具有常量引用参数的函数模板专用化
- C++:常量引用参数
- 字符串引用参数的效率C++
- 通过非常量引用参数修改常量引用参数
- 如何将指针变量作为引用参数传递?
- C++初始化 std::function 时如何将占位符绑定到引用/引用参数?
- 移动类的成员作为常量引用参数传递
- C++带有适用于左值和右值的引用参数的函数
- constexpr 函数的常量引用参数:gcc/msvc vs clang/icc
- 如何使用类型特征将函数的通用引用参数限制为 r 值引用?
- 委托构造函数和引用参数
- 对 const 引用参数使用默认值会导致崩溃
- 为什么我们不允许将纯引用参数传递给 std::thread,但允许传递原始指针?
- 为什么我需要将默认引用参数定义为 const 以便为其分配一个左值?
- 将非左值作为常量引用参数传递.临时是在本地作用域还是在调用方作用域中创建的?
- 如何强制函数仅接受左值引用参数
- 模板引用参数推断失败C++
- 非类型引用参数可以在运行时修改,这是否意味着模板可以在运行时实例化?
- 将unique_ptr作为引用参数或常量传递unique_ptr引用