热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

c++参考——它们只是语法糖吗?-C++references-aretheyjustsyntacticsugar?

IsaC++referencejustsyntacticsugar,ordoesitofferanyspeedupsincertaincases?c++引用仅仅是语法

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?

编辑:在大约六个答案和许多评论之后。我还是认为引用只是一种合成糖。如果人们可以直接回答“是”或“否”,如果有人能给出一个公认的答案?

7 个解决方案

#1


58  

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.

如果引用最终被存储在某个地方,那么它通常以相同的空间作为指针。这并不是说,它将像指针一样使用:如果编译器知道引用绑定到哪个对象,它很可能会对它进行切割。

#2


54  

Assume reference as a pointer that:

假设引用作为指针:

  1. Can't be NULL
  2. 不能空
  3. Once initialized, can't be re-pointed to other object
  4. 初始化后,不能重新指向其他对象
  5. 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 (e.g. pointer to interface type).

至于我,我更喜欢使用引用,只要我不需要nullptr作为一个有效值,值应该被重新指向或传递到不同类型的值(例如,指向接口类型的指针)。

#3


19  

The compiler cannot assume a pointer is non-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.

另一方面,假设引用被实现为指针,编译器仍然可以假设它是非空的,从不改变它指向的位置,并指向一个有效的对象。

#4


10  

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. 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.

一个t&t *const(注意const适用于指针,而不是指向)是相对相似的。获取实际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*). But the compiler is free to neglect the possibility that someone directly accessing the bytes of foo could actually change the value referred to. This permits the compiler to read the reference "pointer" implementation once, and then never read it again. If we had struct foo{ int* x; } the compiler would have to prove each time it did a *f.x that the pointer value had not changed.

sizeof(foo)几乎肯定等于sizeof(int*)。但是编译器可以自由地忽略直接访问foo字节的人实际上可以更改所引用的值的可能性。这允许编译器读取一次引用的“指针”实现,然后再也不读取它。如果我们有struct foo{int* x;编译器每次做*f时都要证明。指针的值没有改变。

If you had 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. The offsets to each local variable are compile-time constants off the stack frame pointer.

如果有一个lambda根据引用捕获数据,而不是通过指针捕获每个值,那么它只能捕获堆栈帧指针。每个局部变量的偏移量都是堆栈帧指针之外的编译时常量。

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. So those have to be captured by pseudo-pointer.

异常是引用捕获的引用,如果引用变量超出范围,则在缺陷报告中对c++必须保持有效。这些必须被伪指针捕获。

For a concrete example (if a toy one):

具体例子(如玩具):

void part( std::vector& v, int left, int right ) {
  std::function op = [&](int y){return yright;};
  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 ints by (basically pointer) reference.

上面的lambda只捕获堆栈帧指针,并知道左和右相对于它的位置,从而减小它的大小,而不是通过(基本上是指针)引用捕获两个ints。

Here we have references implied by [&] whose existence is eliminated easier than if they where pointers captured by value:

这里我们有[&]所暗示的引用,它们的存在比value捕获的指针更容易消除:

void part( std::vector& v, int left, int right ) {
  int* pleft=&left;
  int* pright=&right;
  std::function 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. 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(:)循环的定义依赖于引用的生命周期扩展,以避免不必要的复制,而for(:)循环的用户可以使用auto&&以自动推断出最轻的方式来包装迭代对象。

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

std::array 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. 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.

在这里,引用生命期扩展小心地防止不必要的拷贝发生。如果我们更改make_arr以返回一个arr const&它将继续工作,没有任何副本。如果我们更改get_arr以返回一个容器,该容器返回一个按值大小的大元素(例如,一个输入迭代器范围),那么同样没有必要的复制。

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. 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.

同样,转发引用允许数据被智能地视为const、non-const、lvalue或rvalue。临时数据被标记为临时数据,用户不再需要的数据被标记为临时数据,持久的数据被标记为lvalue引用。

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 -to-lvalue引用转换,就不能形成指向临时引用的指针。

#5


8  

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(一个前哨值)。
  • 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&or auto&&。

Thus, at the language level, a reference is an entity of its own. The rest are implementation details.

因此,在语言水平上,引用是它自己的实体。其余是实现细节。

#6


7  

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! In this case, there literally is no value for p

然而,现代的编译器是如此的优秀,以至于它们现在能够识别出一个指针,它本来可以作为所有意图和目的的引用,并将它当作引用来处理。这可能会导致相当有趣的结果在一个调试器,你可以有一个声明如int * p = x,让调试器打印p的价值,只是说一些的“p不能打印”,因为x是一个寄存器中,编译器是治疗x * 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上执行指针运算,那么您将迫使编译器不再像引用那样优化指针,一切都会变慢)

#7


1  

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**. The type of this variable is pointer to 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. And whether it provides any speedup has already been answered in depth.

因此,要回答你的问题:c++引用不是指针的语法糖。它是否提供任何加速已经得到了深入的回答。

****** 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.

*****对象,它表示有一个内存地址的任何实例。即使指针是对象,函数也是对象(因此我们有嵌套的指针和函数指针)。在类似的意义上,我们没有要引用的指针,因为它们没有被实例化。


推荐阅读
  • 不同优化算法的比较分析及实验验证
    本文介绍了神经网络优化中常用的优化方法,包括学习率调整和梯度估计修正,并通过实验验证了不同优化算法的效果。实验结果表明,Adam算法在综合考虑学习率调整和梯度估计修正方面表现较好。该研究对于优化神经网络的训练过程具有指导意义。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • 本文介绍了[从头学数学]中第101节关于比例的相关问题的研究和修炼过程。主要内容包括[机器小伟]和[工程师阿伟]一起研究比例的相关问题,并给出了一个求比例的函数scale的实现。 ... [详细]
  • LeetCode笔记:剑指Offer 41. 数据流中的中位数(Java、堆、优先队列、知识点)
    本文介绍了LeetCode剑指Offer 41题的解题思路和代码实现,主要涉及了Java中的优先队列和堆排序的知识点。优先队列是Queue接口的实现,可以对其中的元素进行排序,采用小顶堆的方式进行排序。本文还介绍了Java中queue的offer、poll、add、remove、element、peek等方法的区别和用法。 ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
  • 本文详细介绍了如何使用MySQL来显示SQL语句的执行时间,并通过MySQL Query Profiler获取CPU和内存使用量以及系统锁和表锁的时间。同时介绍了效能分析的三种方法:瓶颈分析、工作负载分析和基于比率的分析。 ... [详细]
  • 第四章高阶函数(参数传递、高阶函数、lambda表达式)(python进阶)的讲解和应用
    本文主要讲解了第四章高阶函数(参数传递、高阶函数、lambda表达式)的相关知识,包括函数参数传递机制和赋值机制、引用传递的概念和应用、默认参数的定义和使用等内容。同时介绍了高阶函数和lambda表达式的概念,并给出了一些实例代码进行演示。对于想要进一步提升python编程能力的读者来说,本文将是一个不错的学习资料。 ... [详细]
  • 本文讨论了一个数列求和问题,该数列按照一定规律生成。通过观察数列的规律,我们可以得出求解该问题的算法。具体算法为计算前n项i*f[i]的和,其中f[i]表示数列中有i个数字。根据参考的思路,我们可以将算法的时间复杂度控制在O(n),即计算到5e5即可满足1e9的要求。 ... [详细]
author-avatar
悲酥清风茶
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有