21

正如标题中所指出的那样

4

3 回答 3

20

编译器供应商通常会将引用实现为指针。指针的大小往往与许多内置类型相同或更大。对于这些内置类型,无论您是按值传递还是通过引用传递,都将传递相同数量的数据。函数中,为了获取实际数据,您需要取消引用此内部指针。这可以向生成的代码添加指令,并且您还将有两个可能不在缓存中的内存位置。差异不会太大 - 但可以在紧密循环中测量。

当它们用于内置类型时,编译器供应商可以选择忽略 const 引用(有时也包括非 const 引用)——这一切都取决于编译器在处理函数及其调用者时可用的信息。

于 2011-03-18T00:38:51.320 回答
9

对于 int、char、short 和 float 等 pod 类型,数据的大小与传入以引用实际数据的地址相同(或更小)。在引用的地址查找值是一个不必要的步骤,并增加了额外的成本。

例如,采用以下函数foobar

void foo(char& c) {...}
void bar(char c) {...}

foo被调用时,地址是通过 32 位或 64 位的值传递的,具体取决于您的平台。当您使用cinside时,foo您需要查找传入地址中保存的数据的值。

调用时bar传入一个 char 大小的值,并且没有地址查找开销。

于 2011-03-18T00:43:36.843 回答
4

在实践中,C++ 实现通常通过在底层传递一个指针来实现按引用传递(假设调用不是内联的)。

所以没有聪明的机制可以让按引用传递更快,因为传递指针并不比传递小值快。一旦你在函数中,按值传递也可以从更好的优化中受益。例如:

int foo(const int &a, int *b) {
    int c = a;
    *b = 2;
    return c + a;
}

编译器都知道,b指向a,这称为“别名”。已经a通过值传递,这个函数可以优化到等价于*b = 2; return 2*a;. 在现代 CPU 的指令流水线中,这可能更像是“开始 a 加载,开始 b 存储,等待 a 加载,乘以 2,等待 b 存储,返回”,而不是“开始 a 加载,开始 b 存储” ,等待 a 加载,等待 b 存储,开始加载,等待 a 加载,将 a 添加到 c,返回”,然后您开始了解为什么潜在的混叠会对性能产生重大影响。在某些情况下,如果不一定会在这一方面产生巨大影响。

当然,混叠只会在它改变函数对某些可能输入的效果的情况下阻碍优化。但仅仅因为您对该函数的意图是别名不应影响结果,并不一定意味着编译器可以假设它不会:有时实际上,在您的程序中,不会发生别名,但编译器不会不知道。并且不必有第二个指针参数,只要您的函数调用优化器“看不到”的代码,它就必须假设任何引用都可能更改。

于 2011-03-18T01:09:51.633 回答