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对于引用意味着绝对没有优化器) ;这个词只是为了帮助程序员 - 我个人不太确定它是如此大的帮助,但这是另一个故事).
Pet*_*der 14
正如Oli所说,返回a const T&而不是T完全不同的东西,并且在某些情况下可能会中断(如他的例子中所示).
以const T&相对于普通T的一种说法是不太可能打破东西,但仍然有几个重要的区别.
T代替的const T&要求T是拷贝构造.T将调用复制构造函数,这可能很昂贵(以及函数退出时的析构函数).T允许您将参数修改为局部变量(可以比手动复制更快).const T&由于未对准的临时性和间接成本,采取可能会更慢.如果被调用者和调用者是在单独的编译单元中定义的,则编译器无法优化远离引用.例如,我编译了以下代码:
#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作为参数进行test1中edi.第2行和第3行将结果推送到主存储器并将地址放在第一个参数中,因为参数被声明为对int的引用,因此必须可以例如获取其地址.是否可以使用寄存器完全计算某些东西或者需要访问主存储器这些天可以产生很大的不同.因此,除了更多的类型,const int&也可以更慢.经验法则是,通过值传递最多与字大小一样大的所有数据,并通过引用传递所有其他数据.还通过引用传递模板化参数; 由于编译器可以访问模板的定义,因此可以始终优化引用.
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.
| 归档时间: |
|
| 查看次数: |
30588 次 |
| 最近记录: |