一个引用可能比一个指针更有效吗?

8

我想知道在某些情况下(取决于平台、编译器或代码环境等),引用是否比指针更高效?

6个回答

11

引用是否比指针更高效?

不是的!并不一定。标准也没有这样说。

大多数编译器通过使用指针实现引用机制。C++中添加引用是为了支持运算符重载,而不是为了提高效率。


1
@Guillaume07: Bjarne Stroustrup [C++的历史:1979-1991]说:“引用主要是为了支持运算符重载而引入的。C通过值传递每个函数参数,如果通过值传递对象会很低效或不合适,则用户可以传递指针。这种策略在使用运算符重载时不起作用。在这种情况下,符号的简便性是必不可少的,以便用户不必插入大型对象的地址。” - Prasoon Saurav
1
@Prasoon:你应该把这个引语加入到答案本身中。 - Nawaz
问题是“引用可能更快”。我不明白我们两个怎么可能知道每个存在的实现足够多,以至于说“不,它可能永远不会更快”。说“它不一定更快”与说“暗示它可能更快是错误的”完全不同。因此,我认为问题的直接答案必须是“是”,尽管当然讨论它是否真的发生很有用。 - Steve Jessop
1
@Steve:我没有说“是”的原因是我不知道哪些编译器实现了引用机制而没有使用指针,并且这样的实现更有效率? - Prasoon Saurav
1
啊。我没有说“不”正是出于同样的原因。 - Steve Jessop
显示剩余4条评论

7

是的,可能会。我们只有在标准中存在一个明确或暗示的要求参考速度必须与所有情况下等效指针一样慢或更慢时才能说“不能”。由于标准不涉及这种性能细节,因此没有这样的要求。

例如,在低优化级别下,这将是合理的:

int n = 0;
int *np = &n;
for (int i = 0; i < 10000000; ++i) {
    *np += i;
}
std::cout << n;

稍微慢一些,比这个速度慢一点。
int n = 0;
int &nr = n;
for (int i = 0; i < 10000000; ++i) {
    nr += i;
}
std::cout << n;

仅仅因为在第一种情况下编译器可以更容易地检测到它完全可以避免间接寻址 - 在所有范围内,nr明确是n的别名,而* np仅仅因为我们初始化后从未对np进行赋值,所以仍然是n的别名。低优化结果可能会在指针情况下产生更多间接寻址和一个效率较低的循环。
但是,请查看编译器生成的代码 - 没有优化的gcc为我生成了相同的代码,它似乎在两种情况下都发现了别名,并使用寄存器来存储总数和堆栈插槽i。
当然,您希望一个好的优化编译器“理解”两种情况中的别名,并发出将n存储在寄存器中的代码,并且在循环中只触摸该寄存器(而不是内存)。

6

您可以将NULL传递给指针,因此您必须检查指针变量是否为NULL。这是引用比指针更高效的唯一原因。

除此之外,编译器将引用实现为指针。


2
你不必要进行检查。你可以将函数的行为在输入为空时记录为未定义。 - Oliver Charlesworth
@Oli 不,你不必检查,但这总是好的,因为NULL是指针的有效值。 - BЈовић
@VJo:确实。但这并不意味着您必须进行检查。只需在注释中输入“NULL值会导致未定义的行为”。在这些情况下,最多可以放置一个assert。 - Oliver Charlesworth
@Oli 即使是断言也是一种检查。但我们正在谈论微优化 :P - BЈовић
1
标准库函数不执行此类检查。 - user2100815
@Neil 不是的,如果你不应该传递NULL,那么会发生什么是众所周知的 :) - BЈовић

5

是的,引用比指针更有效率。原因在于优化器。一般来说,现代CPU中最昂贵的操作之一就是从内存中读取数据。而在调用(非内联)函数后,编译器必须假设任何可能发生变化的内存变量都已经发生了变化。这意味着在下面的例子中,在调用Bar()后,它必须重新加载p

class Foo {
  int* p;
  void Bar(); // defined somewhere else, not visible to compiler.
public:
  Foo() { p = new int; Bar(); std::cout << *p; }
};

现在,这里的问题不是必须重新加载*p,而是必须重新加载p。它也可能会改变。现在看一下下面的代码:

class Foo {
  int* p; // !
  void Bar(); // defined somewhere else, not visible to compiler.
public:
  Foo() { p = new int; int& r = *p; Bar(); std::cout << r; }
};

现在,编译器发现在Bar()之后没有使用p。使用的是r,而且它可能被实现为指针。但是它不能被重新分配!因此,优化程序可以选择在函数调用期间保存r


4

C++引用在底层工作时类似于“永久解引用”指针。我预计性能应该是相同的。但是,由于引用的指针不能被重新分配,编译器可能能够应用更多无法用于原始指针的优化。


很好的发现优化。但我有些怀疑,因为优化器应该能够检测变量是否被修改(gcc和Clang都在中间表示中使用SSA形式来实现这一点)。 - Matthieu M.
@Matthieu:这取决于情况,有些情况下编译器无法排除调用某些代码并具有指向指针的指针的可能修改指针本身的情况。由于引用不能被重新设置,一旦调用返回,其引用对象保证仍然是相同的,因此理论上,如果(例如)其地址存储在调用方保存的寄存器中,则可以获得优势。如果指针值存储在调用方保存的寄存器中,则其值在调用后已过时,并且必须从实际指针对象重新加载。理论上。 - Steve Jessop
@Steve:逃逸分析的乐趣 :/ - Matthieu M.

3

除了直接访问,没有什么比指针更有效率的了。但速度并不是引用存在的原因。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接