通过const ref传递整数类型有什么好处

时间:2009-11-13 06:47:24

标签: c++ reference

问题:通过const引用传递整数类型是否有益,而不是简单地通过值传递。

void foo(const int& n); // case #1

VS

void foo(int n); // case #2

对于用户定义的类型,答案是明确的,情况#1避免了在确保对象的常量的同时进行不必要的复制。但是在上面的例子中,引用和整数(至少在我的系统上)是相同的大小,所以我无法想象在函数调用需要多长时间方面存在很大差异(由于复印)。但是,我的问题实际上与编译器内联函数有关:

对于非常小的内联函数,编译器是否必须在#2的情况下复制整数?通过让编译器知道我们不会更改引用,它可以内联函数调用而无需不必要地复制整数吗?

欢迎任何建议。

12 个答案:

答案 0 :(得分:12)

通过const ref传递内置的int类型实际上是一个小的去优化(通常)。至少对于非内联函数。编译器可能必须实际传递必须被解引用的指针才能获取值。您可能认为它总是可以优化它,但是别名规则和支持单独编译的需要可能会迫使编译器出手。

但是,对于您的第二个问题:

  

对于非常小的内联函数,编译器是否必须在#2的情况下复制整数?通过让编译器知道我们不会更改引用,它可以内联函数调用而无需不必要地复制整数吗?

如果语义允许,编译器应该能够优化副本或解除引用,因为在那种情况下,编译器完全了解调用站点和函数实现的状态。它可能只是将值加载到寄存器中,并且在完成参数时只需将寄存器用于其他内容。当然,所有这些都非常依赖于函数的实际实现。

答案 1 :(得分:7)

这取决于编译器,但我希望任何合理的优化器都会给你相同的结果。

我用gcc测试过,结果确实相同。这是我测试的代码:

inline int foo(const int& n) {
  return n * 2;
}

int bar(int x) {
  int y = foo(x);
  return y;
}

(有和没有const& on foo的n参数)

然后使用以下命令行使用gcc 4.0.1编译:

g++ -O3 -S -o foo.s foo.cc

两个编辑的输出是相同的。

答案 2 :(得分:6)

当有人像这样使用const引用作为基本数据类型时,我发现它很烦人。我无法看到这样做的任何好处,尽管可能有人认为对于大于sizeof(pointer)的数据类型,它可能更有效。虽然,我真的不关心这种微小的“优化”。

答案 3 :(得分:4)

通常不值得。即使对于内联函数,编译器也不会是愚蠢的。我唯一一次说它是合适的,如果你有一个模板;仅仅为了获取副本而不是引用而专门为内置函数做出额外的努力可能是不值得的。

答案 4 :(得分:2)

在编写或使用模板时,最终可能会出现(const int&),因为模板编写器无法知道实际的类型。如果对象是重量级的,传递引用是正确的做法;如果它是int或其他东西,编译器可能能够将其优化掉。

在没有某种外部要求的情况下,通常没有理由为一次性功能做这样的事情 - 它只是额外的打字,而且抛出引用实际上往往会抑制优化。在寄存器中复制小数据比在内存中重新加载它要便宜得多!

答案 5 :(得分:2)

很多人都说两者之间没有区别。我可以看到一个(也许是人为的)案例,其中差异很重要......

int i = 0;

void f(const int &j)
{
    i++;

    if (j == 0)
    {        
        // Do something.
    }
}

void g()
{
    f(i);
}

但是......正如其他人提到的那样......整数和指针的大小可能相似。对于小到整数的东西,引用会降低您的性能。它可能不会明显,除非你的方法被大量调用,但它会在那里。另一方面,在某些情况下,编译器可能会优化它。

答案 6 :(得分:2)

您可以使用boost::call_traits<your type>::param_type进行最佳参数传递。这默认为基本类型的简单参数传递,并通过结构和类的const引用。

答案 7 :(得分:1)

我想不出任何好处。我甚至看到过建议在编写模板时,使用元编程按值传递整数类型,并且只对非整数类型使用const引用。

答案 8 :(得分:1)

不要这样做。 int与常见的32位平台上的指针/引用大小相同,而64位上的大小相同,因此您可以获得性能劣势而不是获益。我的意思是,所有函数参数都按顺序被压入堆栈,以便函数可以读取它们,并且它将是你的int,或者在引用的情况下它的地址。另一个缺点是被调用者将通过间接访问n(取消引用地址),或者它将在其堆栈上进行复制作为优化。

如果对按值传递的int进行某些更改,则可能会将其写回到传递它的堆栈上的位置,或写入新的堆栈位置。第二种情况自然不是有利的,但不应该发生。通过限制你自己做出这样的改变,但这对const int也是一样的。

在正确的内联案例中,自然并不重要,但请记住,并非所有内联编写的内容都是。

答案 9 :(得分:0)

引用的成本通常与整数类型的成本相同,但是对于引用,您必须具有间接性,因为对某些内存的引用必须被解析为值。

只需按值复制,坚持使用内置类型的不可变约定。

答案 10 :(得分:0)

这不仅仅是表现。 一个真实的故事:本周我注意到一位同事试图改进数字食谱并取代宏


    #define SHFT(a,b,c,d) do { (a)=(b); (b)=(c); (c)=(d); } while (0)

通过此功能


    inline void Rotate(double& dFirst, double& dSecond, double& dThird, const double dNewValue)
    {
        dFirst = dSecond;
        dSecond = dThird;
        dThird = dNewValue;
    }   // Function Rotate

如果他通过引用传递了最后一个参数,那将会有效,但实际上,这个代码

 
        Rotate(dum,*fb,*fa,dum);

应该交换* fa和* fb不再有效。 无法通过引用传递它是不可能的,因为在其他地方非-l值被传递给最后一个参数。

答案 11 :(得分:0)

请阅读Dave Abrahams的Want Speed? Pass by Value