[英]C++ references - are they just syntactic sugar?
C ++参考只是语法糖,还是在某些情况下提供任何加速?
例如,无论如何,按指针调用都涉及到一个副本,并且对于逐个引用调用似乎也是如此。 潜在的机制似乎是相同的。
编辑:经过大约六个答案和许多评论。 我仍然认为参考文献只是合成糖。 如果人们可以直接回答是或否,如果有人可以接受答案?
假设引用为指针:
任何使用它的尝试都会隐含地取消引用它:
int a = 5; int &ra = a; int *pa = &a; ra = 6; (*pa) = 6;
这里看起来像反汇编:
int a = 5;
00ED534E mov dword ptr [a],5
int &ra = a;
00ED5355 lea eax,[a]
00ED5358 mov dword ptr [ra],eax
int *pa = &a;
00ED535B lea eax,[a]
00ED535E mov dword ptr [pa],eax
ra = 6;
00ED5361 mov eax,dword ptr [ra]
00ED5364 mov dword ptr [eax],6
(*pa) = 6;
00ED536A mov eax,dword ptr [pa]
00ED536D mov dword ptr [eax],6
从编译器的角度来看,分配给引用与分配给解除引用的指针是一回事。 正如您所看到的,它们之间没有区别(我们现在不讨论编译器优化)但是如上所述,引用不能为空并且对它们包含的内容有更强的保证。
至于我,我更喜欢使用引用,只要我不需要nullptr
作为有效值,应该重新定义的值或要传递给不同类型的值(例如指向接口类型的指针)。
引用具有比指针更强的保证,因此编译器可以更积极地进行优化。 我最近看到GCC完全通过函数引用内联多个嵌套调用,但不是通过函数指针的单个嵌套调用(因为它无法证明指针总是指向同一个函数)。
如果引用最终存储在某处,则它通常占用与指针相同的空间。 这并不是说,它将像指针一样被使用:如果编译器知道引用绑定到哪个对象,编译器可能会很好地通过它。
编译器不能假设指针是非null的; 在优化代码时,它必须要么证明指针是非空的,要么发出一个程序来解释它为空的可能性(在明确定义的上下文中)。
同样,编译器也不能假设指针永远不会改变值。 (它也不能假设指针指向一个有效的对象,虽然我很难想象一个在明确定义的上下文中这很重要的情况)
在另一方面,假设引用,作为指针实现,编译器仍然可以假设它是不为空,永远不会改变它指向,并指向一个有效的对象。
引用与指针的不同之处在于,您无法对引用执行某些操作并将其定义为行为。
您不能获取引用的地址,而只能获取引用的地址。 创建后,您无法修改引用。
一个T&
和一个T*const
(注意const
适用于指针,而不是指向那里),它们相对类似。 获取实际const
值的地址并对其进行修改是未定义的行为,因为修改(它直接使用的任何存储)引用。
现在,在实践中,您可以获得一个参考存储:
struct foo {
int& x;
};
sizeof(foo)
几乎肯定等于sizeof(int*)
。 但编译器可以自由地忽略直接访问foo
字节的人实际可能改变所引用的值的可能性。 这允许编译器一次读取引用“指针”实现,然后再也不读它。 如果我们有struct foo{ int* x; }
struct foo{ int* x; }
编译器将不得不每次它做了一个证明*fx
指针的值没有改变。
如果你有struct foo{ int*const x; }
struct foo{ int*const x; }
再次启动在其不可改变的表现的参考样(修改被宣布一些const
是UB)。
我不知道任何编译器编写者使用的技巧是在lambda中压缩引用捕获。
如果你有一个lambda通过引用捕获数据,而不是通过指针捕获每个值,它只能捕获堆栈帧指针。 每个局部变量的偏移量是堆栈帧指针之外的编译时常量。
例外是通过引用捕获的引用,即使引用变量超出范围,在C ++的缺陷报告下也必须保持有效。 所以那些必须由伪指针捕获。
一个具体的例子(如果玩具一个):
void part( std::vector<int>& v, int left, int right ) {
std::function<bool(int)> op = [&](int y){return y<left && y>right;};
std::partition( begin(v), end(v), op );
}
上面的拉姆达可以捕获仅堆栈帧指针,知道left
和right
是相对于它,减少它的尺寸,而不是捕获2个int
S按(基本上指针)参考。
这里我们有[&]
隐含的引用,它们的存在比它们通过值捕获的指针更容易被删除:
void part( std::vector<int>& v, int left, int right ) {
int* pleft=&left;
int* pright=&right;
std::function<bool(int)> op = [=](int y){return y<*pleft && y>*pright;};
std::partition( begin(v), end(v), op );
}
引用和指针之间还有一些其他差异。
引用可以延长临时的生命周期。
这在for(:)
循环中大量使用。 for(:)
循环的定义都依赖于引用生命周期扩展以避免不必要的副本,而for(:)
循环的用户可以使用auto&&
自动推导出最轻的权重方式来包装迭代对象。
struct big { int data[1<<10]; };
std::array<big, 100> arr;
arr get_arr();
for (auto&& b : get_arr()) {
}
这里引用生命周期扩展小心地防止不必要的副本发生。 如果我们改变make_arr
以返回一个arr const&
并且它继续工作而没有任何副本。 如果我们更改get_arr
以返回一个按值返回big
元素的容器(例如,输入迭代器范围),则不会再进行任何不必要的复制。
这在某种意义上是语法糖,但它允许相同的构造在许多情况下是最佳的,而不必根据事物的返回或迭代进行微观优化。
类似地,转发引用允许数据被智能地视为const,非const,左值或右值。 临时工具被标记为临时工具,用户不再需要的数据被标记为临时数据,将持久存储的数据标记为左值参考。
这里的优势引用超过了非引用,你可以形成一个临时的rvalue引用,并且你不能形成一个指向那个临时的指针而不通过rvalue引用到左值引用转换。
没有
引用不仅仅是语法差异; 它们也有不同的语义 :
nullptr
(sentinel值)。 auto const&
或auto&&
。 因此,在语言层面,引用是它自己的实体。 其余的是实施细节。
过去有效率优势,因为编译器更容易参考优化。 然而,现代编译器已经变得如此优秀,以至于不再有任何优势。
超指针的一个巨大优势参考是引用可以引用寄存器中的值,而指针只能指向内存块。 获取寄存器中的某些内容的地址,然后强制编译器将该值放入普通的内存位置。 这可以在紧密循环中创造巨大的好处。
但是,现代编译器非常好,以至于它们现在可以识别出一个指针,它可以作为所有意图和目的的参考,并将其视为与引用完全相同。 这可能会在调试器中产生相当有趣的结果,您可以在其中使用诸如int* p = &x
类的语句,要求调试器打印p
的值,只是让它说出“p无法打印”的内容。因为x
实际上是在寄存器中,并且编译器将*p
视为对x
的引用! 在这种情况下, p
实际上没有价值
(但是,如果你试图在p
上进行指针运算,那么你就会强制编译器不再优化指针,就像引用一样,并且一切都会变慢)
8.3.2参考文献[dcl.ref]
引用可以被认为是对象的名称
这与保存Object **的内存位置地址的变量(不同于引用)的指针不同。 此变量的类型是指向Object的指针。
内部引用可以实现为指针,但标准永远不会保证。
所以回答你的问题:C ++ Reference不是指针的语法糖。 它是否提供任何加速已经得到了深入的回答。
******这里的对象是指任何具有内存地址的实例。 甚至指针都是对象,因此函数也是如此(因此我们有嵌套指针和函数指针)。 在类似的意义上,我们没有指向引用,因为它们没有实例化。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.