C ++函数参数:使用引用还是指针(然后取消引用)?

时间:2009-10-30 16:12:56

标签: c++ pointers performance

我得到了一些代码,其中一些参数是指针,然后指针被解引用 提供价值。我担心指针取消引用会花费周期,但看了之后 以前的StackOverflow文章:How expensive is it to dereference a pointer?,也许没关系。

以下是一些例子:


bool MyFunc1(int * val1, int * val2)
{
    *val1 = 5;
    *val2 = 10;
    return true;
}

bool MyFunc2(int &val1, int &val2)
{
    val1 = 5;
    val2 = 10;
    return true;
}

我个人更喜欢传递参考作为一种风格问题,但更好的是一个版本 (就工艺周期而言)比另一个?

11 个答案:

答案 0 :(得分:20)

我的经验法则是,如果参数可以为NULL,则传递指针,即在上述情况下是可选的,如果参数永远不应为NULL则引用。

答案 1 :(得分:14)

从性能的角度来看,它可能并不重要。其他人已经回答了这个问题。

话虽如此,我还没有发现在这种情况下增加指令会产生显着差异的情况。 我确实意识到,对于一个被称为数十亿次的函数,它可能会产生影响。 作为一项规则,您不应该将编程风格用于这种“优化”。

答案 2 :(得分:10)

您可以从编译器获取汇编代码并进行比较。至少在GCC中,他们生成相同的代码。

答案 3 :(得分:5)

这会因为它是Old Skool而被拒绝,但我经常更喜欢指针,因为它更容易只看一眼代码并查看我传递给函数的对象是否可以被修改,特别是如果它们是像int这样的简单数据类型和漂浮。

答案 4 :(得分:5)

有关使用参考与指针参数的不同指南,根据不同的要求量身定制。在我看来,应该在通用C ++开发中应用的最有意义的规则如下:

  1. 重载运算符时使用引用参数。 (在这种情况下,你实际上别无选择。这就是首先引入的参考资料。)

  2. 对复合(即逻辑“大”)输入参数使用const-reference。 I.e input 参数应该通过值(“原子”值)或const-reference(“聚合”值)传递。使用输出参数和输入输出参数的指针。 不要对输出参数使用引用

  3. 将上述内容纳入帐户,程序中绝大多数参考参数应为const-references。如果您有一个非const引用参数并且它不是运算符,请考虑使用指针代替。

    遵循上述约定,您将能够在调用时看到函数是否可以修改其中一个参数:可能已修改的参数将使用显式&或已存在的方式传递指针。

    还有一个流行的规则是声明可以为null的东西应该作为指针传递,而不能为null的东西应该作为引用传递。我可以想象,这可能在一些非常狭窄和非常具体的情况下有意义,但总的来说这是一个主要的反规则。只是不要这样做。如果你想表达一些指针不能为空的事实,请将相应的断言作为函数的第一行。

    至于性能考虑,通过指针或通过引用传递绝对没有性能差异。两种参数在物理层面上完全相同。即使函数内联,现代编译器也应该足够聪明以保持等价。

答案 5 :(得分:3)

这是使用g ++生成的程序集的区别。 a.cpp是指针,b.cpp是引用。

$ g++ -S a.cpp

$ g++ -S b.cpp

$ diff a.S b.S
1c1
<       .file   "a.cpp"
---
>       .file   "b.cpp"
4,6c4,6
< .globl __Z7MyFunc1PiS_
<       .def    __Z7MyFunc1PiS_;        .scl    2;      .type   32;     .endef
< __Z7MyFunc1PiS_:
---
> .globl __Z7MyFunc1RiS_
>       .def    __Z7MyFunc1RiS_;        .scl    2;      .type   32;     .endef
> __Z7MyFunc1RiS_:

只是功能名称略有不同;内容相同。当我使用g++ -O3时,我的结果相同。

答案 6 :(得分:1)

引用与指针非常相似,但有一个很大区别:引用不能为NULL。所以你不需要检查它们是否是可用的对象(比如指针)。

因此我假设编译器会生成相同的代码。

答案 7 :(得分:1)

所有其他答案都已经指出,在运行时性能方面,这两种功能都没有优于其他功能。

但是,我认为前者的功能在可读性方面优于其他功能,因为像

这样的调用
f( &a, &b );

清楚地表达了对某个变量的引用(对我来说,这个'对象可能被函数'bell修改)。接受引用而不是指针的版本看起来像

f( a, b );

在调用之后看到a发生了变化,这是相当令人惊讶的,因为我无法从调用中看出该变量是通过引用传递的。

答案 8 :(得分:1)

从性能角度来看,任何有能力的编译器都应该消除这个问题,这在任何情况下都不太可能成为瓶颈。如果您真的在这么低的水平上工作,那么对现实数据进行汇编代码分析和性能分析将成为您工具包的重要组成部分。

从维护的角度来看,你真的不应该允许某人将参数作为指针传递而不检查它们是否为null。

所以你最终写了大量的空检查代码,没有充分的理由。

基本上来自:

  1. 创建对象
  2. 以非const引用传入
  3. 要:

    1. 创建对象
    2. 获取对象的地址
    3. 传递地址
    4. 检查地址是否指向空
    5. 取消引用回非const引用以在整个函数中使用
    6. 虽然编译器会解析所有垃圾,但代码的读者不会。在扩展函数或计算代码的功能时,需要考虑额外的代码。还会编写更多代码,这会增加可能包含错误的行数。正如它的指针一样,错误更可能是古怪的未定义行为错误。

      它还鼓励较弱的程序员编写这样的代码:

      int* a = new int(4); // Don't understand why this has to be a pointer
      int* b = new int(5); // Don't understand why this has to be a pointer
      MyFunc2(a, b);
      int& a_r = *a;
      int& b_r = *b;
      

      是的,这太糟糕了,但我从新编码员那里看到了它,他们并不真正理解指针模型。

      与此同时,我认为,考虑到潜在的损失,整个“我可以看到它是否会在没有查看实际标题的情况下进行修改”,这是一个错误的优势。如果您无法立即知道代码上下文中的输出参数是哪个,那么这就是您的问题,并且没有多少指针可以保存您。如果你必须有一个&amp;为了确定您的输出参数,我可以介绍一下:

      MyFunc2(/*&*/a, /*&*/b)
      

      &符号的所有“可读性”,没有相关的指针风险。

      但是,在维护方面,一致性为王。如果你正在集成的现有代码,它作为指针传递(例如类或库的其他功能),没有充分的理由成为一个疯狂的反叛者并走自己的路。那真的会引起混淆。

答案 9 :(得分:0)

您应该看到为目标机器生成的汇编代码...考虑到函数调用总是在恒定时间内完成,而在实际机器上,时间真的可忽略不计......

答案 10 :(得分:0)

如果你需要做下面的事情,请记住&#34; a&#34;将在功能上进行更改。什么会阻止你反转具有相同类型或产生一些讨厌错误的参数,当你必须调用某个函数时,你必须将原型保留在你的内存中,或者使用一些在工具提示中显示它的IDE。没有任何借口,我不喜欢参考,因为我不知道该函数是否会改变它,不是一个有效的论据。

MyFunc2(/*&*/a, /*&*/b)