【问题标题】:C++: How do I decide if to pass params by ref or by value?C++:我如何决定是通过引用还是通过值传递参数?
【发布时间】:2012-03-15 13:53:58
【问题描述】:

使用 C++,我如何决定是通过值还是通过引用/指针传递参数? (告诉我 32 位和 64 位的答案)让我们拿 A. 2 个 32 位值作为指向 32 位值的指针是更多还是更少?

在我看来,我总是应该按价值传递。 C 我认为我应该按值传递,但有人告诉我(但我没有看到证据)处理器不处理值而不是它们的位大小,所以它需要更多的工作。因此,如果我将它们传递给传递,那么按值传递是否会做更多的工作,因此 byref 更快?最后我扔了一个枚举。我认为枚举应该始终按值计算

注意:当我说 ref 时,我的意思是 const 引用或指针(不能忘记 const...)

struct A { int a, b; }
struct B { int a; }
struct C { char a, b; }
enum D   { a,b,c }
void fn(T a);

如果我多次推送参数并且代码不使用尾调用,现在告诉我答案? (假设这些值直到 4 次左右调用深度才使用)

【问题讨论】:

标签: c++ performance struct pass-by-reference pass-by-value


【解决方案1】:

忘记堆栈大小。如果要更改,则应通过引用传递,否则应按值传递。

防止由于允许函数意外更改数据而引入的错误比浪费几个字节的堆栈空间重要得多。

如果堆栈空间成为问题,请停止使用这么多级别(例如用迭代解决方案替换递归解决方案)或扩展堆栈。四级递归通常并不麻烦,除非你的结构很大或者你在嵌入式世界中运行。

如果性能成为问题,请找到更快的算法 :-) 如果不可能,那么您可以查看通过引用传递,但您需要了解它违反了调用者和被调用者。如果你能忍受它,那没关系。我一般不能:-)

值/引用二分法的目的是控制在语言级别作为参数传递的东西会发生什么,而不是摆弄语言的实现工作方式。

【讨论】:

  • 我目前的经验法则始终是内置插件的按值(float、double、long、short 等)。对于类和结构总是 const& (你永远不知道它们什么时候改变)。但是我正在生成代码,所以当我生成代码但我不阅读代码时,我可以检查类的大小。除非我编写自己的测试并多次调用它,否则我不知道如何分析它。无论哪种方式都可能无关紧要,但是有人曾经给我一个有趣的答案,关于在从堆栈调用/返回时省略复制构造函数,这是我想问这个问题的唯一原因
【解决方案2】:

为了一致性,我通过引用传递所有参数,包括内置函数(当然,在可能的情况下使用const)。

我确实在性能关键领域进行了测试——与内置函数相比,最坏情况下的损失是微不足道的。对于非内置函数,以及当调用很深(作为概括)时,引用可能会快很多。这对我来说很重要,因为我正在做相当多的深度 TMP,其中函数体很小。

如果您计算的是指令、硬件寄存器不足(例如嵌入式),或者如果函数不适合内联,则您可能会考虑打破该约定。

很遗憾,您提出的问题比看起来要复杂得多——答案可能因您的平台、ABI、调用约定、寄存器计数等而有很大差异。

【讨论】:

    【解决方案3】:

    很大程度上取决于您的要求,但最佳做法是通过引用传递,因为它可以减少内存占用。

    如果您按值传递大对象,则会在内存中制作它的副本,并调用复制构造函数来制作它的副本。

    因此会花费更多的机器周期,而且,如果您按值传递,更改不会反映在原始对象中。

    所以尝试通过引用传递它们。

    希望这对您有所帮助。

    问候,肯

    【讨论】:

    • 没有帮助。你没有提到通过 C 是否是一个较低的足迹,也没有谈论工作 cpu 明智
    • 欢迎来到 Stack Overflow。 StackOverflow 不是文本消息系统,没有理由不正确拼写单词(例如,“you”而不是“u”)。
    • @James,为你整理好了 :-) 顺便说一句,肯,我不同意你的观点。这些结构都没有构造函数,并且适当的封装应该双向工作(保护向下和向上)。不足以让你失望,但足以提及它:-)
    • 是的,在这种情况下,它们没有构造函数,但内存是个问题,而且更改不会反映在原始结构中
    【解决方案4】:

    首先,引用和指针不一样。

    按指针传递

    如果有任何/部分适用,则通过指针传递参数:

    1. 传递的元素可能为空。
    2. 资源在被调用函数内部分配,调用者应该负责释放这样的资源。请记住在这种情况下为该资源提供一个 free() 函数。
    3. 该值是变量类型,例如void*。当它的类型在运行时或取决于使用模式(或隐藏实现 - 即 Win32 HANDLE)确定时,例如线程过程参数。 (这里支持 c++ 模板和 std::function,并且仅当您的环境不允许时才为此目的使用指针。

    通过引用传递

    如果其中任何/部分适用,则通过引用传递参数:

    1. 大多数时候。 (更喜欢通过 const 引用传递)
    2. 如果您希望对传递的参数的修改对调用者可见。 (除非使用 const 引用)。
    3. 如果传递的参数从不为空。
    4. 如果您知道传递的参数类型是什么并且您可以控制函数的签名。

    通过副本传递

    如果其中任何/部分适用,请传递一份副本:

    1. 通常尽量避免这种情况。
    2. 如果要对传递的参数的副本进行操作。也就是说,您知道被调用的函数无论如何都会创建一个副本。
    3. 原始类型小于系统指针大小 - 因为与 const ref 相比,它没有性能/内存差异。
    4. 这很棘手 - 当您知道该类型实现了移动构造函数时(例如 C++11 中的 std::string)。然后它看起来好像你正在通过副本。

    这三个列表中的任何一个都可以更长,但这些是 - 我想说 - 基本的经验法则。

    【讨论】:

    • 我认为-即使有移动构造函数,您也应该通过 ref 传递。移动构造函数适用于变量是临时变量(可能经常发生)的返回类型之类的情况。但是我的一个问题表明 2 是非常正确的。但是,当您必须将其传递 X 深度时,我不确定 #2(无论如何都需要副本时按值传递)是否为真
    • #2 是在您知道实现并且知道不太可能改变的情况下。例如 void Func(int times) { while(--times >= 0) doSomething(); }
    【解决方案5】:

    您的完整问题对我来说有点不清楚,但我可以回答您何时使用按值传递或按引用传递。

    当按值传递时,您会将参数的完整副本放入调用堆栈。这就像您在函数调用中创建一个局部变量,并使用您传递给它的任何内容进行初始化。

    当通过引用传递时,你...好吧,通过引用传递。主要区别在于可以修改外部对象。

    减少通过引用传递的大对象的内存负载的好处。对于基本数据类型(例如 32 位或 64 位整数),性能可以忽略不计。

    一般来说,如果您打算使用 C/C++ 工作,您应该学习使用指针。将对象作为参数传递几乎总是通过指针(相对于引用)传递。您绝对必须使用引用的少数实例位于复制构造函数中。您也希望在运算符中使用它,但这不是必需的。

    【讨论】:

      【解决方案6】:

      按值复制对象通常是个坏主意——更多的 CPU 来执行构造函数;为实际对象提供更多内存。使用const 防止函数修改对象。函数签名应该告诉调用者引用的对象可能会发生什么。

      intcharpointers 之类的东西通常是按值传递的。

      至于您概述的结构,按值传递并不重要。您需要进行分析才能找到答案,但在程序的宏伟计划中,您最好在其他地方寻找以提高 CPU 和/或内存方面的性能。

      【讨论】:

        【解决方案7】:

        在您担心优化之前,我会考虑您是否需要值或引用语义。通常,如果您希望您调用的方法能够修改参数,您将通过引用传递。在这种情况下,您可以像在 C 中一样传递指针,但惯用的 C++ 倾向于使用引用。

        没有规定小类型或枚举应始终按值传递。有很多代码传递int& 参数,因为它们依赖于通过引用传递的语义。此外,您应该记住,对于任何相对较小的数据类型,您不会注意到按引用传递和按值传递之间的速度差异。

        也就是说,如果您有一个非常大的结构,您可能不想复制很多副本。这就是const 引用很方便的地方。请记住,尽管 C++ 中的 const 并未严格执行(即使它被认为是不好的做法,您也可以随时将 const_cast 去掉)。没有理由将const int& 传递给int,尽管有理由将const ClassWithManyMembers& 传递给ClassWithManyMembers

        如果您打算将它们视为值,我会说您列出的所有结构都可以按值传递。考虑一下,如果你调用一个函数,它接受一个struct Rectangle{int x, y, w, h} 类型的参数,这与独立传递这四个参数是一样的,这真的没什么大不了的。一般来说,你应该更担心复制构造函数必须做的工作——例如,通过值传递vector 可能不是一个好主意,因为它必须动态分配内存并遍历一个大小为你不知道,然后调用更多的复制构造函数。

        虽然您应该记住所有这些,但一个好的一般规则是:如果您想要引用语义,请通过引用传递。否则,通过值传递内在函数,通过 const 引用传递其他内容。

        此外,C++11 引入了 r 值引用,这使事情变得更加复杂。但这是一个不同的话题。

        【讨论】:

          【解决方案8】:

          这些是我使用的规则:

          • 对于本机类型:
            • 当它们是输入参数时按值
            • 当它们是强制输出参数时通过非常量引用
          • 对于结构或类:
            • 当它们是输入参数时通过 const 引用
            • 当它们是输出参数时通过非常量引用
          • 对于数组:
            • 当它们是输入参数时通过 const 指针(const 适用于数据,而不是此处的指针,即const TYPE *
            • 当它们是输出参数时按指针(const 适用于数据,而不是指针)

          我发现很少有需要对上述规则进行例外处理的情况。想到的一个例外是可选的结构或类参数,在这种情况下引用将不起作用。在这种情况下,我使用 const 指针(输入)或非 const 指针(输出),这样你也可以传递 0。

          【讨论】:

            【解决方案9】:

            如果你想要一个副本,然后按值传递。如果要更改它并且希望在函数之外看到这些更改,则通过引用传递。如果您想要速度并且不想更改它,请通过 const 引用传递。

            【讨论】:

            • +0(几乎 -1)bc 我要求优化,这不是在谈论影响它的几件事。
            猜你喜欢
            • 2020-04-01
            • 2014-07-26
            • 1970-01-01
            • 2012-02-03
            • 1970-01-01
            • 1970-01-01
            • 2015-02-24
            • 2016-06-11
            • 1970-01-01
            相关资源
            最近更新 更多