简体   繁体   English

C ++参考 - 它们只是语法糖吗?

[英]C++ references - are they just syntactic sugar?

Is a C++ reference just syntactic sugar, or does it offer any speed ups in certain cases? C ++参考只是语法糖,还是在某些情况下提供任何加速?

For example, a call-by-pointer involves a copy anyway, and that seems to be true about a call-by-reference as well. 例如,无论如何,按指针调用都涉及到一个副本,并且对于逐个引用调用似乎也是如此。 The underlying mechanism appears to be the same. 潜在的机制似乎是相同的。

Edit: After about six answers and many comments. 编辑:经过大约六个答案和许多评论。 I am still of the opinion references are just syntatic sugar. 我仍然认为参考文献只是合成糖。 If people could answer in a straight yes or no, and if someone could do an accepted answer? 如果人们可以直接回答是或否,如果有人可以接受答案?

Assume reference as a pointer that: 假设引用为指针:

  1. Can't be NULL 不能为NULL
  2. Once initialized, can't be re-pointed to other object 一旦初始化,就无法重新指向其他对象
  3. Any attempt to use it will implicitly dereference it: 任何使用它的尝试都会隐含地取消引用它:

     int a = 5; int &ra = a; int *pa = &a; ra = 6; (*pa) = 6; 

here as it looks in disassembly: 这里看起来像反汇编:

    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  

the assigning to the reference is the same thing from the compiler perspective as the assigning to a dereferenced pointer. 从编译器的角度来看,分配给引用与分配给解除引用的指针是一回事。 There are no difference between them as you can see (we are not talking about compiler optimization right now) However as mentioned above, references can't be null and have stronger guarantees of what they contains. 正如您所看到的,它们之间没有区别(我们现在不讨论编译器优化)但是如上所述,引用不能为空并且对它们包含的内容有更强的保证。

As for me, I prefer using references as long as I don't need nullptr as a valid value, values that should be repointed or values of different types to be passed into (eg pointer to interface type). 至于我,我更喜欢使用引用,只要我不需要nullptr作为有效值,应该重新定义的值或要传递给不同类型的值(例如指向接口类型的指针)。

References have stronger guarantees than pointers, so the compiler can optimize more aggressively. 引用具有比指针更强的保证,因此编译器可以更积极地进行优化。 I've recently seen GCC inline multiple nested calls through function references perfectly, but not a single one through function pointers (because it couldn't prove that the pointer was always pointing at the same function). 我最近看到GCC完全通过函数引用内联多个嵌套调用,但不是通过函数指针的单个嵌套调用(因为它无法证明指针总是指向同一个函数)。

If the reference ends up stored somewhere, it typically takes the same space as a pointer. 如果引用最终存储在某处,则它通常占用与指针相同的空间。 That is not to say, again, that it will be used like a pointer : the compiler may well cut through it if it knows which object the reference was bound to. 这并不是说,它将像指针一样被使用:如果编译器知道引用绑定到哪个对象,编译器可能会很好地通过它。

The compiler cannot assume a pointer is non-null; 编译器不能假设指针是非null的; when optimizing code, it has to either prove the pointer is non-null, or emit a program that accounts for the possibility that it is null (in a context where that would be well-defined). 在优化代码时,它必须要么证明指针是非空的,要么发出一个程序来解释它为空的可能性(在明确定义的上下文中)。

Similarly, the compiler cannot assume the pointer never changes value. 同样,编译器也不能假设指针永远不会改变值。 (nor can it assume the pointer points to a valid object, although I'm having trouble imagining a case where that would matter in a well-defined context) (它也不能假设指针指向一个有效的对象,虽然我很难想象一个在明确定义的上下文中这很重要的情况)

On the other hand, assuming that references are implemented as pointers, the compiler is still allowed to assume it is non-null, never changes where it points, and points to a valid object. 在另一方面,假设引用,作为指针实现,编译仍然可以假设它是不为空,永远不会改变它指向,并指向一个有效的对象。

References differ from pointers in that there are things you cannot do to a reference and have it be defined behavior. 引用与指针的不同之处在于,您无法对引用执行某些操作并将其定义为行为。

You cannot take the address of a reference, but only what is referred to. 您不能获取引用的地址,而只能获取引用的地址。 You cannot modify a reference once it is created. 创建后,您无法修改引用。

A T& and a T*const (note that const applies to the pointer, not the pointed-to, there) are relatively similar. 一个T&和一个T*const (注意const适用于指针,而不是指向那里),它们相对类似。 Taking the address of an actual const value and modifying it is undefined behavior, as is modifying (any storage that it uses directly) a reference. 获取实际const值的地址并对其进行修改是未定义的行为,因为修改(它直接使用的任何存储)引用。

Now, in practice, you can get a the storage of a reference: 现在,在实践中,您可以获得一个参考存储:

struct foo {
  int& x;
};

sizeof(foo) will almost certainly equal sizeof(int*) . sizeof(foo)几乎肯定等于sizeof(int*) But the compiler is free to neglect the possibility that someone directly accessing the bytes of foo could actually change the value referred to. 但编译器可以自由地忽略直接访问foo字节的人实际可能改变所引用的值的可能性。 This permits the compiler to read the reference "pointer" implementation once, and then never read it again. 这允许编译器一次读取引用“指针”实现,然后再也不读它。 If we had struct foo{ int* x; } 如果我们有struct foo{ int* x; } struct foo{ int* x; } the compiler would have to prove each time it did a *fx that the pointer value had not changed. struct foo{ int* x; }编译器将不得不每次它做了一个证明*fx指针的值没有改变。

If you had struct foo{ int*const x; } 如果你有struct foo{ int*const x; } struct foo{ int*const x; } is again starts behaving reference-like in its immutability (modifying something that was declared const is UB). struct foo{ int*const x; }再次启动在其不可改变的表现的参考样(修改被宣布一些const是UB)。


A trick that I'm not aware of any compiler writers using is to compress reference-capture in a lambda. 我不知道任何编译器编写者使用的技巧是在lambda中压缩引用捕获。

If you have a lambda that captures data by reference, instead of capturing each value via a pointer, it could capture only the stack frame pointer. 如果你有一个lambda通过引用捕获数据,而不是通过指针捕获每个值,它只能捕获堆栈帧指针。 The offsets to each local variable are compile-time constants off the stack frame pointer. 每个局部变量的偏移量是堆栈帧指针之外的编译时常量。

The exception is references captured by reference, which under a defect report to C++ must remain valid even if the reference variable goes out of scope. 例外是通过引用捕获的引用,即使引用变量超出范围,在C ++的缺陷报告下也必须保持有效。 So those have to be captured by pseudo-pointer. 所以那些必须由伪指针捕获。

For a concrete example (if a toy one): 一个具体的例子(如果玩具一个):

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 );
}

the lambda above could capture only the stack frame pointer, and know where left and right are relative to it, reducing it size, instead of capturing two int s by (basically pointer) reference. 上面的拉姆达可以捕获仅堆栈帧指针,知道leftright是相对于它,减少它的尺寸,而不是捕获2个int S按(基本上指针)参考。

Here we have references implied by [&] whose existence is eliminated easier than if they where pointers captured by value: 这里我们有[&]隐含的引用,它们的存在比它们通过值捕获的指针更容易被删除:

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 );
}

There are a few other differences between references and pointers. 引用和指针之间还有一些其他差异。

A reference can extend the lifetime of a temporary. 引用可以延长临时的生命周期。

This is used heavily in for(:) loops. 这在for(:)循环中大量使用。 Both the definition of the for(:) loop relies on reference lifetime extension to avoid needless copies, and users of for(:) loops can use auto&& to automatically deduce the lightest weight way to wrap the iterated objects. for(:)循环的定义都依赖于引用生命周期扩展以避免不必要的副本,而for(:)循环的用户可以使用auto&&自动推导出最轻的权重方式来包装迭代对象。

struct big { int data[1<<10]; };

std::array<big, 100> arr;

arr get_arr();

for (auto&& b : get_arr()) {
}

here reference lifetime extension carefully prevents needless copies from ever occuring. 这里引用生命周期扩展小心地防止不必要的副本发生。 If we change make_arr to return a arr const& it continues to work without any copies. 如果我们改变make_arr以返回一个arr const&并且它继续工作而没有任何副本。 If we change get_arr to return a container that returns big elements by-value (say, an input iterator range), again no needless copies are done. 如果我们更改get_arr以返回一个按值返回big元素的容器(例如,输入迭代器范围),则不会再进行任何不必要的复制。

This is in a sense syntactic sugar, but it allows the same construct to be optimal in many cases without having to micro-optimize based on how things are returned or iterated over. 这在某种意义上是语法糖,但它允许相同的构造在许多情况下是最佳的,而不必根据事物的返回或迭代进行微观优化。


Similarly, forwarding references allow data to be treated as a const, non-const, lvalue or rvalue intelligently. 类似地,转发引用允许数据被智能地视为const,非const,左值或右值。 Temporaries are marked as temporaries, data that users have no further need for is marked as temporary, data that will persist is marked as being an lvalue reference. 临时工具被标记为临时工具,用户不再需要的数据被标记为临时数据,将持久存储的数据标记为左值参考。

The advantage references have over non-references here is that you can form a rvalue reference to a temporary, and you cannot form a pointer to that temporary without passing it through an rvalue reference-to-lvalue reference conversion. 这里的优势引用超过了非引用,你可以形成一个临时的rvalue引用,并且你不能形成一个指向那个临时的指针而不通过rvalue引用到左值引用转换。

No 没有


References are not just a syntactic difference; 引用不仅仅是语法差异; they also have different semantics : 它们也有不同的语义

  • A reference always aliases an existing object, unlike a pointer which may be nullptr (a sentinel value). 引用总是别名现有对象,不像指针可能是nullptr (sentinel值)。
  • A reference cannot be re-seated, it always points to the same object throughout its lifetime. 引用无法重新定位,它始终指向同一个对象。
  • A reference can extend the lifetime of an object, see binding to auto const& or auto&& . 引用可以延长对象的生命周期,请参阅绑定到auto const&auto&&

Thus, at the language level, a reference is an entity of its own. 因此,在语言层面,引用是它自己的实体。 The rest are implementation details. 其余的是实施细节。

There used to be efficiency advantages because references are easier for the compiler to optimize. 过去有效率优势,因为编译器更容易参考优化。 However, modern compilers have gotten so good at it that there is no longer any advantage. 然而,现代编译器已经变得如此优秀,以至于不再有任何优势。

One huge advantage references have over pointers is that a reference can refer to a value in a register, while pointers can only point at blocks of memory. 超指针的一个巨大优势参考是引用可以引用寄存器中的值,而指针只能指向内存块。 Take the address of something which would have been in a register, and you would force the compiler to put that value into a normal memory location instead. 获取寄存器中的某些内容的地址,然后强制编译器将该值放入普通的内存位置。 This can create tremendous benefits in tight loops. 这可以在紧密循环中创造巨大的好处。

However, modern compilers are so good that they now recognize a pointer that could have been a reference for all intents and purposes, and treat it exactly the same as if it were a reference. 但是,现代编译器非常好,以至于它们现在可以识别出一个指针,它可以作为所有意图和目的的参考,并将其视为与引用完全相同。 This can cause rather intriguing results in a debugger, where you can have a statement such as int* p = &x , ask the debugger to print the value of p , only to have it say something along the lines of "p cannot be printed" because x was actually in a register, and the compiler was treating *p as a reference to x ! 这可能会在调试器中产生相当有趣的结果,您可以在其中使用诸如int* p = &x类的语句,要求调试器打印p的值,只是让它说出“p无法打印”的内容。因为x实际上是在寄存器中,并且编译器将*p视为对x的引用! In this case, there literally is no value for p 在这种情况下, p实际上没有价值

(However, if you tried to do pointer arithmetic on p , you would then force the compiler to no longer optimize the pointer to act like a reference does, and everything would slow down) (但是,如果你试图在p上进行指针运算,那么你就会强制编译器不再优化指针,就像引用一样,并且一切都会变慢)

8.3.2 References [dcl.ref] 8.3.2参考文献[dcl.ref]

A reference can be thought of as a name of an object 引用可以被认为是对象的名称

which is different from pointers which is a variable (unlike reference) that holds the address of a memory location of an Object ** . 这与保存Object **的内存位置地址的变量(不同于引用)的指针不同。 The type of this variable is pointer to Object. 此变量的类型是指向Object的指针。

Internally Reference may be implemented as pointer, but standard never guaranteed so. 内部引用可以实现为指针,但标准永远不会保证。

So to answer your question: C++Reference are not syntactic sugar to pointers. 所以回答你的问题:C ++ Reference不是指针的语法糖。 And whether it provides any speedup has already been answered in depth. 它是否提供任何加速已经得到了深入的回答。

****** Object here it means any instance that has a memory address. ******这里的对象是指任何具有内存地址的实例。 Even pointers are Objects and so are functions (and thus we have nested pointers and function pointers). 甚至指针都是对象,因此函数也是如此(因此我们有嵌套指针和函数指针)。 In similar sense, we do not have pointers to reference as they are not instantiated. 在类似的意义上,我们没有指向引用,因为它们没有实例化。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM