int vs const int&

我注意到我通常使用常量引用作为返回值或参数。 我认为这是因为它和在代码中使用非引用几乎一样。 但是它肯定需要更多的空间,函数声明变得更长。 我可以用这样的代码,但我认为有些人认为这是一种糟糕的编程风格。

你怎么看? 是否值得写const int& over int ? 我认为它已经被编译器优化了,所以也许我只是在浪费我的时间来编码它。

在C ++中,我认为使用const T&的反模式非常普遍,就像在处理参数时只是说T 。 然而,一个值和一个引用(不pipe是否是const)是两个完全不同的东西,总是盲目地使用引用而不是值会导致微妙的错误。

原因在于,在处理引用时,您必须考虑两个不存在值的问题: 生命周期别名

作为一个例子,应用这种反模式的地方是标准库本身,其中std::vector<T>::push_back接受一个const T&而不是一个值作为参数, :

 std::vector<T> v; ... if (v.size()) v.push_back(v[0]); // Add first element also as last element 

这个代码是一个滴答作响的炸弹,因为std::vector::push_back一个const引用,但是执行push_back可能需要重新分配,如果发生这种情况意味着在重新分配之后收到的引用将不再有效( 生命期问题)进入未定义的行为领域。

别名问题也是一个微妙的问题,如果使用const引用而不是值的来源。 我已经被这样的代码咬了:

 struct P2d { double x, y; P2d(double x, double y) : x(x), y(y) {} P2d& operator+=(const P2d& p) { x+=px; y+=py; return *this; } P2d& operator-=(const P2d& p) { x-=px; y-=py; return *this; } }; struct Rect { P2d tl, br; Rect(const P2d& tl, const P2d& br) : tl(tl), bt(br) {} Rect& operator+=(const P2d& p) { tl+=p; br+=p; return *this; } Rect& operator-=(const P2d& p) { tl-=p; br-=p; return *this; } }; 

代码看起来乍一看很安全, P2d是一个二维点, Rect是一个矩形,加/减一个点意味着翻译矩形。

但是,如果要将原始的矩形翻译回原点,可以使用myrect -= myrect.tl; 该代码将不起作用,因为翻译运算符已被定义为接受引用在该情况下引用同一个实例的成员。

这意味着在用tl -= p;更新topleft后tl -= p; (0, 0)因为p只是对左上angular成员的引用,所以右下angular的更新将不起作用(0, 0)p也将同时变成(0, 0)因为它会把它翻译成(0,0)因此基本上什么都不做。

请不要因为const字而认为const引用就像是一个值。 这个词的存在只是为了给你编译错误,如果你试图改变引用的对象使用该引用 ,但并不意味着被引用的对象是常量。 更具体地说,由const ref引用的对象可以改变(例如,由于混叠 ),甚至可以在使用它( 生命期问题)的时候变得不存在。

const T& const中,const表示引用的属性,而不是引用对象的属性:它是不可能使用它来更改对象的属性。 大概只读会是一个更好的名字,因为const有海事组织的作用,推动这个观点,即当你使用这个参考时,这个对象将会是不变的。

你当然可以通过使用引用而不是复制值来获得令人印象深刻的加速,特别是对于大类。 但是在使用引用时,您应该总是考虑别名和生命期问题,因为在封面下他们只是指向其他数据的指针。 对于“本地”数据types(整数,双精度,指针)引用,但实际上会比值更慢,并没有什么可以获得使用它们而不是值。

另外一个const引用总是意味着优化器的问题,因为编译器被强制为偏执的,并且每次执行任何未知的代码时,它必须假定所有引用的对象可能现在具有不同的值(对于引用, const绝对没有意思优化器;这个词只是为了帮助程序员 – 我个人不太确定这是一个很大的帮助,但这是另一个故事)。

正如奥利所说,返回一个const T&而不是T是完全不同的东西,并且在某些情况下(如他的例子)可能会中断。

const T&而不是普通T作为参数不太可能破坏事物,但仍然有几个重要的区别。

  • T代替const T&要求T是可复制的。
  • 采取T将调用复制构造函数,这可能是昂贵的(也是函数退出的析构函数)。
  • 采取T允许您修改该参数作为本地variables(可以比手动复制更快)。
  • 考虑到const T&可能会因为临时alignment和间接成本而变慢。

int &int不可互换! 特别是,如果您返回对本地堆栈variables的引用,则行为是未定义的,例如:

 int &func() { int x = 42; return x; } 

可以返回一个对函数结尾处不会被销毁的东西的引用(例如一个静态的或者一个类的成员)。 所以这是有效的:

 int &func() { static int x = 42; return x; } 

和外部世界,直接返回int (除了你现在可以修改它,这就是为什么你看到const int &很多)的效果相同。

引用的优点是不需要副本,这对于处理大类对象很重要。 但是,在很多情况下,编译器可以优化这些; 请参阅http://en.wikipedia.org/wiki/Return_value_optimization

如果被调用者和调用者是在不同的编译单元中定义的,那么编译器不能优化引用。 例如,我编译了下面的代码:

 #include <ctime> #include <iostream> int test1(int i); int test2(const int& i); int main() { int i = std::time(0); int j = test1(i); int k = test2(i); std::cout << j + k << std::endl; } 

在优化级别为3的64位Linux上使用G ++。第一次调用不需要访问主内存:

 call time movl %eax, %edi #1 movl %eax, 12(%rsp) #2 call _Z5test1i leaq 12(%rsp), %rdi #3 movl %eax, %ebx call _Z5test2RKi 

第1行直接使用eax的返回值作为edi test1参数。 第2行和第3行将结果推送到主存储器中,并将地址放在第一个参数中,因为参数被声明为int的引用,所以它必须可以取其地址。 无论是使用寄存器完全计算还是需要访问主内存,这些日子都会有很大的不同。 因此,除了更多types, const int&也可以更慢。 经验法则是,通过值传递最多与字大小相等的所有数据,并且通过引用const传递所有其他数据。 也通过引用const传递模板参数; 由于编译器可以访问模板的定义,因此可以始终优化引用。

为什么没有“思考”它被编译器优化了,为什么你不能得到汇编器列表,并找出肯定?

junk.c ++:

 int my_int() { static int v = 5; return v; } const int& my_int_ref() { static int v = 5; return v; } 

生成的汇编程序输出(消除):

 _Z6my_intv: .LFB0: .cfi_startproc .cfi_personality 0x3,__gxx_personality_v0 movl $5, %eax ret .cfi_endproc 

 _Z10my_int_refv: .LFB1: .cfi_startproc .cfi_personality 0x3,__gxx_personality_v0 movl $_ZZ10my_int_refvE1v, %eax ret 

这两个movl指令是非常不同的。 第一个移动到EAX (恰好是传统上用于返回x86 C代码中的值的寄存器),第二个将variables的地址(为了清晰起见而省略)移到EAX 。 这意味着在第一种情况下调用函数可以直接使用寄存器操作,而不需要使用内存来使用答案,而在第二种情况下它必须通过返回的指针来访问内存。

所以看起来它没有被优化。

这超出了你在这里给出的其他答案,解释了为什么Tconst T&不可互换。