C ++的参考 – 他们只是语法糖?
是一个C ++参考只是语法糖,还是在某些情况下提供任何加速?
例如,一个指针的调用无论如何都涉及到一个副本,而且对于一个引用调用来说也是如此。 基本的机制似乎是相同的。
编辑:约六个答案和许多意见。 我仍然认为参考只是syntally糖。 如果人们可以直接回答“是”或“否”,并且有人可以接受答案?
引用比指针有更强的保证,所以编译器可以更积极地进行优化。 我最近看到GCC通过函数引用很好地embedded了多个嵌套的调用,但没有通过函数指针(因为它不能certificate指针总是指向相同的函数)。
如果引用最终存储在某个地方,它通常需要与指针相同的空间。 这并不是说,它会像指针一样被使用:如果编译器知道引用被绑定到哪个对象,那么编译器就可以很好地切换它。
假定引用为一个指针:
- 不能为NULL
- 一旦初始化,就不能重新指向其他对象
-
任何使用它的尝试都会隐含地引用它:
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
从编译器的angular度来看,分配给引用的操作与分配给解除引用的指针是一样的。 你可以看到它们之间没有区别(我们现在不是在讨论编译器优化)。但是如上所述,引用不能为null,并且对它们包含的内容有更强的保证。
至于我,我更喜欢使用引用,只要我不需要nullptr
作为一个有效值,应该被重新指定的值或不同types的值传入(例如指向接口types)。
编译器不能假定指针是非空的; 当优化代码时,它必须要么certificate指针是非空的,要么发出一个程序来说明它是空的可能性(在一个很好定义的上下文中)。
同样,编译器不能假定指针永远不会改变值。 (也不能假定指针指向一个有效的对象,尽pipe我很难想象一个在一个明确定义的上下文中会起作用的情况)
另一方面,假设引用是作为指针实现的,编译器仍然可以假定它是非空的,永远不会改变它指向的地方,并指向一个有效的对象。
引用与指针的不同之处在于,有些事情你不能对引用做任何事情,并将其定义为行为。
你不能把参考地址,而只是参考的地址。 一旦创build,就不能修改参考。
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; }
再次开始在其不变性中performance引用types(修改被声明为const
东西是UB)。
我不知道任何编译器编写者使用的技巧是压缩lambda中的引用捕获。
如果你有一个lambda通过引用捕获数据,而不是通过指针捕获每个值,它只能捕获堆栈帧指针。 每个局部variables的偏移量都是堆栈帧指针的编译时常量。
例外情况是引用捕获的引用,即使引用variables超出范围,向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 ); }
上面的lambda只能捕获堆栈帧指针,并知道left
和right
相对于它,减小了它的大小,而不是通过(基本指针)引用捕获两个int
。
在这里,我们有[&]
隐含的引用,它们的存在被消除了,比在指针被值捕获的地方更容易:
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
元素的容器(比如说一个input迭代器范围),那么就不会有不必要的副本。
这在某种意义上是语法糖,但是它允许在许多情况下,相同的构造是最优的,而不必根据事物的返回或迭代的方式进行微观优化。
类似地,转发引用允许将数据智能地视为const,非const,左值或右值。 临时标记为临时标记,用户没有进一步需要的数据被标记为临时的,将持续的数据标记为左值参考。
优点引用超过非引用在这里是你可以形成一个临时的右值引用,你不能通过一个右值引用左值引用转换传递给临时指针。
没有
参考不仅仅是一个句法上的区别。 他们也有不同的语义 :
- 一个引用总是将一个已存在的对象进行别名,而不像一个可能是
nullptr
(一个前哨值)的指针。 - 参考文献不能重新定位,它始终指向同一个对象。
- 引用可以延长对象的生命周期,请参阅绑定到
auto const&
或auto&&
。
因此,在语言层面,引用是它自己的一个实体。 其余的是实现细节。
曾经有效率的优势,因为参考编译器更容易优化。 然而,现代编译器已经变得如此优秀,以致没有任何优势。
引用超过指针的一个巨大优点是引用可以引用寄存器中的值,而指针只能指向内存块。 拿一些寄存器中的东西的地址,然后强制编译器将该值放入正常的内存位置。 这可以在紧密的循环中创造巨大的好处。
然而,现代编译器非常好,现在他们认识到一个指针可以作为所有意图和目的的参考,并且把它看作是一个参考。 这会在debugging器中产生相当有趣的结果,在这种debugging器中可以使用诸如int* p = &x
这样的语句,要求debugging器输出p
的值,只是让它沿着“p不能打印”因为x
实际上是在一个寄存器中,编译器将*p
作为对x
的引用! 在这种情况下,字面上没有任何价值
(但是,如果你试图在p
上进行指针运算,那么你将会迫使编译器不再优化指针来像一个引用那样工作,而且一切都会变慢)
8.3.2参考[dcl.ref]
参考可以被认为是一个对象的名称
这与指针是不同的,它是一个variables(不同于引用),它保存了一个Object **的内存位置的地址。 这个variables的types是指向Object的指针。
内部引用可以作为指针来实现,但标准从未保证如此。
所以要回答你的问题:C ++ Reference不是指针语法糖。 而且是否提供加速function已经得到了深入的回答。
******这里的对象表示任何有内存地址的实例。 即使指针是对象也是函数(因此我们有嵌套的指针和函数指针)。 在类似的意义上,我们没有指向引用的指针,因为它们没有实例化。