Last Updated: 2023-12-26 11:12:26 Tuesday
-- TOC --
C++引入了一种新的类型,叫做reference类型,它与指针有些类似,比指针更安全。
References are a major improvement to handling pointers. They’re similar to pointers, but with some key differences.
reference是一种新的类型,它与指针有如下区别:
从C++11开始,reference分为lvalue和rvalue,本文下面的内容,全都是针对lvalue。(rvalue主要用于move semantics)
lvalue的三大功能:
定义reference类型,使用ampersand符号&
:
#include <cstdio>
int main(void) {
int abc{123};
auto &to_abc = abc;
to_abc = 234;
printf("%d %d\n", abc, to_abc);
return 0;
}
to_abc就是abc的一个reference,修改to_abc的值,等同于修改abc的值。这段代码会打印出来两个234:
$ g++ test_ref.cpp
$ ./a.out
234 234
Under the hood, references are equivalent to pointers because they’re also a zero-overhead abstraction. The compiler produces similar code.
上面测试代码,反汇编之后:
0000000000401126 <main>:
401126: 55 push %rbp
401127: 48 89 e5 mov %rsp,%rbp
40112a: 48 83 ec 10 sub $0x10,%rsp
40112e: c7 45 f4 7b 00 00 00 movl $0x7b,-0xc(%rbp)
401135: 48 8d 45 f4 lea -0xc(%rbp),%rax
401139: 48 89 45 f8 mov %rax,-0x8(%rbp)
40113d: 48 8b 45 f8 mov -0x8(%rbp),%rax
401141: c7 00 ea 00 00 00 movl $0xea,(%rax)
401147: 48 8b 45 f8 mov -0x8(%rbp),%rax
40114b: 8b 10 mov (%rax),%edx
40114d: 8b 45 f4 mov -0xc(%rbp),%eax
401150: 89 c6 mov %eax,%esi
401152: bf 10 20 40 00 mov $0x402010,%edi
401157: b8 00 00 00 00 mov $0x0,%eax
40115c: e8 cf fe ff ff call 401030 <printf@plt>
401161: b8 00 00 00 00 mov $0x0,%eax
401166: c9 leave
401167: c3 ret
下面是注释版:
push %rbp # push调用main接口代码的rbp
mov %rsp,%rbp # rsp --> rbp,新的rbp
sub $0x10,%rsp # rsp - 16,开辟的栈帧空间
movl $0x7b,-0xc(%rbp) # -0xc(%rbp)这个位置是abc变量,存放123
lea -0xc(%rbp),%rax # 将-0xc(%rbp)放入rax,即将abc的地址放入rax
mov %rax,-0x8(%rbp) # 将rax的值存入-0x8(%rbp),这就是to_abc的地址
mov -0x8(%rbp),%rax # 将to_abc指向的地址,存入rax
movl $0xea,(%rax) # 将234存入rax指向的地址,这个地址就是abc的地址
mov -0x8(%rbp),%rax
mov (%rax),%edx
mov -0xc(%rbp),%eax
mov %eax,%esi
mov $0x402010,%edi
mov $0x0,%eax
call 401030 <printf@plt>
mov $0x0,%eax
leave
ret
简单的汇编知识,还是很有必要的,不熟悉的同学,可以学习一点汇编知识。
分析了汇编发现,其实就是地址。只是reference类型,在编译的时候,编译器对待引用类型,与对待指针类型,有不一样的编译。
由于reference类型不可以出现空值,因此在编写代码的时候,在某些时候,会比使用指针简单一点点。
void test(myobject *p) {
if (p == NULL)
return;
// 开始使用p指针,p->something...or (*p).something...
}
void test(myobject& p){
// 直接使用对象p,p.something...
}
使用指针的时候,为了安全,在接口入口处,一般都要判断一下指针是否为空。而使用reference,就不需要判断了,直接上。这的确会减少一些overhead!这是reference带来的一个好处。另一个好处是,使用reference,不需要使用member-of-pointer操作符->
,直接用.
这个符号。
当一个函数接口的参数是reference的时候,传参也变得简单了,直接传入对象本身。返回reference时,也是直接返回对象(avoiding a copy)。请看下面的测试代码:
$ cat test_ref.cpp
#include <cstdio>
struct abc {
int a;
int b;
};
abc& change(abc& g) {
g.a = 4;
g.b = 5;
return g;
}
int main(void) {
abc a{1,2};
abc& h = change(a);
printf("%d %d\n", h.a, h.b);
return 0;
}
$ g++ test_ref.cpp
$ ./a.out
4 5
跟指针一样,下面的代码是错的,返回了一个局部栈空间的地址:
// error code
HolmesIV& not_dinkum() {
HolmesIV mike;
return mike;
}
RVO和NRVO是出现在返回对象的场景下。
有一些编码场景,不能使用reference类型,还是只能使用指针!
由于reference类型的变量不能够重新赋值,即不能让它去引用一个别的对象,因此,这个限制也让reference类型的变量,不适合处理链表这样的数据结构。因为,指向链表的指针,在遍历的时候,要不停地变化。
将对象转换成引用
C++有std::ref
,可以用在给线程入口传递引用对象的时候。
一般函数传递引用参数,直接填入对象名称即可,但是线程的情况有些特殊,线程有自己独立的stack。如果线程入口参数是对象,会触发copy+move操作。
这部分细节详情,请参考C++向线程传递对象参数
本文链接:https://cs.pynote.net/sf/c/cpp/202208191/
-- EOF --
-- MORE --