Eri*_*rik 20
编译器供应商通常会将引用实现为指针.指针往往与许多内置类型相同或更大.对于这些内置类型,无论是通过值传递还是通过引用传递,都将传递相同数量的数据.在函数中,为了获得实际数据,您需要取消引用此内部指针.这可以向生成的代码添加指令,并且您还将有两个可能不在缓存中的内存位置.差异不会太大 - 但它可以用紧密循环来衡量.
当编译器供应商在内置类型上使用时,它们可以选择忽略const引用(有时也可以忽略非const引用) - 所有这些都取决于编译器处理函数及其调用者时可用的信息.
小智 9
对于诸如int,char,short和float之类的pod类型,数据的大小与传入以引用实际数据的地址的大小相同(或更小).在引用的地址处查找值是不必要的步骤并增加了额外的成本.
例如,采取以下功能foo和bar
void foo(char& c) {...}
void bar(char c) {...}
Run Code Online (Sandbox Code Playgroud)
当foo被称为地址由32位或者64位或价值传递,根据您的平台.当您c在内部使用时,foo您需要查找传入地址中保存的数据值的成本.
当调用barchar大小的值时,传入并且没有地址查找开销.
实际上,C++ 实现通常通过在底层传递指针来实现引用传递(假设调用不是内联的)。
因此,没有什么聪明的机制可以让引用传递更快,因为传递指针并不比传递小值更快。一旦进入函数,按值传递也可以从更好的优化中受益。例如:
int foo(const int &a, int *b) {
int c = a;
*b = 2;
return c + a;
}
Run Code Online (Sandbox Code Playgroud)
据编译器所知,b指向a,这称为“别名”。通过a值传递,该函数可以优化为与*b = 2; return 2*a;. 在现代CPU的指令管道中,这可能更像是“开始a加载,开始b存储,等待a加载,乘以2,等待b存储,返回”,而不是“开始a加载,开始b存储” ,等待 a 加载,等待 b 存储,开始 a 加载,等待 a 加载,将 a 添加到 c,返回”,然后您开始了解为什么潜在的别名会对性能产生重大影响。在某些情况下,即使不一定会产生巨大的影响。
当然,别名只会在它改变函数对某些可能输入的效果的情况下阻碍优化。但仅仅因为您对该函数的意图是别名不应该影响结果,并不一定意味着编译器可以假设它不会影响结果:有时事实上,在您的程序中,不会发生别名,但编译器不会不知道。并且不必有第二个指针参数,每当您的函数调用优化器“看不到”的代码时,它都必须假设任何引用都可能发生变化。