int vs const int&

Pij*_*usn 57 c++ coding-style const reference

我注意到我通常使用常量引用作为返回值或参数.我认为原因是它与在代码中使用非引用几乎相同.但它肯定需要更多的空间和功能声明变得更长.我很喜欢这样的代码,但我认为有些人发现它编程风格很糟糕.

你怎么看?是否值得编写const int& over int?我认为无论如何它都是由编译器优化的,所以也许我只是在浪费时间编码,一个?

650*_*502 139

在C++中,我认为反模式的使用非常普遍,这种反模式在处理参数时const T&只是简单明了T.然而,值和引用(无论是否为常量)是两个完全不同的东西,并且总是盲目地使用引用而不是值可能导致细微的错误.

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

作为示例,应用此反模式的一个示例是标准库本身,其中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
Run Code Online (Sandbox Code Playgroud)

这个代码是一个定时炸弹,因为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+=p.x; y+=p.y; return *this; }
    P2d& operator-=(const P2d& p) { x-=p.x; y-=p.y; 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; }
};
Run Code Online (Sandbox Code Playgroud)

代码乍一看非常安全,P2d是一个二维点,Rect是一个矩形,加一个点来减去一个点意味着翻译矩形.

但是,如果要在原点中将矩形转换回原点,则编写myrect -= myrect.tl;代码将无法工作,因为已定义转换运算符接受(在这种情况下)引用同一实例的成员的引用.

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

请不要因为这个词而误以为const引用就像一个值const.如果您尝试使用该引用更改引用的对象,则该单词仅用于为您提供编译错误,但并不意味着引用的对象是常量.更具体地说,const ref引用的对象可以改变(例如,由于别名),并且在使用它时甚至可能不存在(生命周期问题).

const T&单词const中表示引用的属性,而不是引用对象的属性:它是不可能使用它来更改对象的属性.可能readonly本来是一个更好的名字,因为const有IMO的心理效应推动了当你使用引用时对象将是恒定的想法.

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

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

  • 很好的答案,我从未想过`push_back()`可能是如此险恶! (14认同)
  • +1非常重要的一点,我认为这是这个问题最重要的答案. (9认同)
  • "[const]的存在只是为了在尝试使用该引用更改引用的对象时给出编译错误,但并不意味着引用的对象是常量"不能说得更好. (4认同)
  • @cldy:当调用`std :: vector :: push_back`时,代码传递对向量的第一个元素的引用,换句话说,就是存储该元素的内存中的地址.`push_back`代码可能需要重新分配向量内容以为新元素腾出空间,并且在进行重新分配之后,它将使用传递的地址作为源来复制构造新条目.但是,如果发生分配,则该地址将是已在其他地方重新分配的已死对象之一.这是一个终身的问题......在执行`push_back`期间,对象被破坏了. (4认同)
  • xanatos:用户必须知道它.当您传递引用而不是值时,您有责任保证被引用对象的生命周期足够.向量的元素不会持续`push_back`的整个执行,因此传递给`push_back`同一向量的元素是违反合同的. (4认同)
  • 可能值得添加的一件事是,当需要复制引用时,引用实际上会导致代码变慢(有时即使不需要复制也是如此).规范性文章是http:// cpp- next.com/archive/2009/08/want-speed-pass-by-value/但是请参阅Chandler Carruth的(非常有趣的)演示,很好地展示了如何通过引用传递会严重损害编译器的优化器:http:// www.youtube.com/watch?v=eR34r7HOU14 (4认同)

Pet*_*der 14

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

const T&相对于普通T的一种说法是不太可能打破东西,但仍然有几个重要的区别.

  • T代替的const T&要求T是拷贝构造.
  • Take T将调用复制构造函数,这可能很昂贵(以及函数退出时的析构函数).
  • Take T允许您将参数修改为局部变量(可以比手动复制更快).
  • const T&由于未对准的临时性和间接成本,采取可能会更慢.

  • 此外,当`T`是一个小类型,例如`int`时,副本可能比参考*便宜*. (9认同)
  • 为什么临时工会不对齐?除非你误入未定义的行为,否则C++中的所有对象都是按照定义对齐的. (5认同)
  • @Peter:你能扩展到最后一点吗?(错位的临时) (3认同)

Phi*_*ipp 8

如果被调用者和调用者是在单独的编译单元中定义的,则编译器无法优化远离引用.例如,我编译了以下代码:

#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;
}
Run Code Online (Sandbox Code Playgroud)

在优化级别为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
Run Code Online (Sandbox Code Playgroud)

1号线直接使用返回值eax作为参数进行test1edi.第2行和第3行将结果推送到主存储器并将地址放在第一个参数中,因为参数被声明为对int的引用,因此必须可以例如获取其地址.是否可以使用寄存器完全计算某些东西或者需要访问主存储器这些天可以产生很大的不同.因此,除了更多的类型,const int&也可以更慢.经验法则是,通过值传递最多与字大小一样大的所有数据,并通过引用传递所有其他数据.还通过引用传递模板化参数; 由于编译器可以访问模板的定义,因此可以始终优化引用.


Oli*_*rth 7

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

int &func()
{
    int x = 42;
    return x;
}
Run Code Online (Sandbox Code Playgroud)

可以返回对函数末尾不会被销毁的内容的引用(例如,静态或类成员).所以这是有效的:

int &func()
{
    static int x = 42;
    return x;
}
Run Code Online (Sandbox Code Playgroud)

和外面的世界一样,具有与int直接返回相同的效果(除了你现在可以修改它,这就是你看到const int &很多的原因).

引用的优点是不需要复制,这对于处理大类对象很重要.但是,在许多情况下,编译器可以优化它; 参见例如http://en.wikipedia.org/wiki/Return_value_optimization.

  • 是的,我知道这两者之间的区别.我的问题是,它是否真的值得使用*const int&*as*int*无论如何应该进行优化. (3认同)
  • @Valdo:在`int`的情况下,在任何理智的平台上肯定没有优势. (2认同)