c++引用——它们只是语法糖吗?
C++ references - are they just syntactic sugar?
c++引用只是语法糖,还是在某些情况下提供任何速度提升?
例如,指针调用无论如何都涉及到一个副本,而引用调用似乎也是如此。潜在的机制似乎是相同的。
编辑:在回答了大约六个问题和许多评论之后。我仍然认为参考文献只是语法糖。如果人们能直接回答"是"或"不是",如果有人能给出一个公认的答案?假设引用是一个指针,
- 不能为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
从编译器的角度来看,对引用的赋值与对解引用指针的赋值是一样的。正如你所看到的,它们之间没有区别(我们现在不是在谈论编译器优化)但是,如上所述,引用不能为空,并且对其包含的内容有更强的保证。
对于我来说,我更喜欢使用引用,只要我不需要nullptr
作为一个有效值,应该重指向的值或传递给不同类型的值(例如指针接口类型)。
引用比指针具有更强的保证,因此编译器可以更积极地进行优化。我最近看到GCC通过函数引用完美地内联了多个嵌套调用,但没有一个通过函数指针(因为它不能证明指针总是指向同一个函数)。
如果引用最终存储在某处,它通常占用与指针相同的空间。这并不是说,它将像指针一样使用:如果编译器知道引用绑定到哪个对象,它很可能会切断它。
编译器不能假定指针非空;在优化代码时,它必须要么证明指针非空,要么发出一个程序来说明它为空的可能性(在一个定义良好的上下文中)。
同样,编译器也不能假定指针永远不会改变值。(它也不能假设指针指向一个有效的对象,尽管我很难想象在一个定义良好的上下文中这有什么关系)
另一方面,假设引用是作为指针实现的,编译器仍然允许假设它是非空的,永远不会改变它指向的位置,并指向一个有效的对象。没有
引用不仅仅是语法上的差异;它们也有不同的语义:
- 一个引用总是别名一个现有的对象,不像一个指针可能是
nullptr
(一个哨兵值)。 - 引用不能重新定位,它在整个生命周期中总是指向同一个对象。
- 引用可以延长对象的生命周期,参见绑定到
auto const&
或auto&&
。
引用与指针的不同之处在于,你不能对引用做一些事情,并使其成为定义行为。
不能取引用的地址,只能取被引用的地址。引用一旦创建,就不能修改。
T&
和T*const
(注意,const
适用于指针,而不是指向对象)是相对相似的。获取实际const
值的地址并修改它是未定义的行为,就像修改(它直接使用的任何存储)引用一样。
现在,在实践中,你可以得到一个引用的存储:
struct foo {
int& x;
};
sizeof(foo)
几乎肯定等于sizeof(int*)
。但是编译器可以自由地忽略直接访问foo
字节的人实际上可能改变所引用的值的可能性。这允许编译器读取一次引用"指针"实现,然后不再读取它。如果我们有struct foo{ int* x; }
,编译器必须在每次执行*f.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 );
}
上面的lambda可以只捕获堆栈帧指针,并且知道left
和right
相对于它的位置,从而减小它的大小,而不是通过(基本上是指针)引用捕获两个int
。
这里我们有[&]
暗示的引用,它们的存在比由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 );
}
引用和指针之间还有一些其他的区别:
引用可以延长临时对象的生命周期。
在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、左值或右值。临时值被标记为临时值,用户不再需要的数据被标记为临时值,将保留的数据被标记为左值引用。
引用相对于非引用的优势在于,您可以形成对临时对象的右值引用,如果不通过右值引用到左值引用的转换传递,则无法形成指向该临时对象的指针。
过去存在效率优势,因为引用更容易被编译器优化。然而,现代的编译器已经非常擅长于此,因此不再有任何优势。
引用相对于指针的一个巨大优势是引用可以指向寄存器中的值,而指针只能指向内存块。取某个本来应该在寄存器中的地址,你会强制编译器将该值放入正常的内存位置。这可以在紧密的循环中创造巨大的好处。
然而,现代的编译器非常好,它们现在可以识别出可以作为引用的指针,并将其视为引用。这可能会在调试器中导致相当有趣的结果,在调试器中,您可以有一个语句,如int* p = &x
,要求调试器打印p
的值,只是让它沿着"p不能打印"的行说一些东西,因为x
实际上是在寄存器中,编译器将*p
视为对x
的引用!在本例中,实际上没有p
的值(然而,如果你试图在p
上做指针运算,你就会迫使编译器不再优化指针,使其像引用一样工作,并且一切都会变慢)
8.3.2 References [dcl.ref]
引用可以看作是对象的名称
不同于指针, 是保存Object**内存位置地址的变量(与引用不同)。该变量的类型是指向Object的指针。
内部引用可以实现为指针,但标准不保证如此。
所以回答你的问题:c++引用不是指针的语法糖。它是否提供任何加速已经得到了深入的回答。
******对象在这里是指任何具有内存地址的实例。甚至指针也是对象,函数也是对象(因此我们有嵌套的指针和函数指针)。类似地,我们没有指针可以引用,因为它们没有实例化。
- 很好的语法来获取对向量/数组数据的大小引用?
- C++:取消引用十六进制值,有点语法问题
- 解释通过从函数引用返回数组的语法
- 将引用绑定到指针的语法是什么?(各种)
- 访问对象的取消引用值的语法
- 统一取消引用语法是否可行?
- 将此类传递给引用的正确语法是什么?
- 解压缩C++指针/引用语法
- 引用变量何时合适,为什么?你能解释一下实际的语法和位置吗?
- C++构造函数中的语法错误 - arugment 是对另一个类中的对象的引用
- C++指针和引用的语法
- C++处理取消引用语法的泛型类
- 是否可以通过从lambda的引用中返回T型对象,而无需使用尾随返回类型语法
- 以 qreal 引用作为参数的语法
- 利用值类型和引用的统一语法
- 用于在C++中返回引用的语法-与号
- Const引用Visual Studio和GCC之间的语法差异
- C++中的指针和引用变量语法顺序
- 为什么"universal references"具有与右值引用相同的语法?
- 语法错误:html 映射文件中的解析错误 - ogmaps 引用错误:找不到变量:GUnload