c函数引用全局变量出错undefined reference_C++ reference_wrapper: Python对象引用在C++中的实现...

6d104414173887c8c52744d9c03b23d3.png

Python的对象引用

众所周知,Python里的大部分对象都是存在堆上,变量名只是对堆上数据的引用,因此可以很轻易地对变量所指向的对象进行替换,如:

# 引用替换

同时,Python的参数传递也是传对象引用(pass-by-object-reference),也就是把对象的指针传过去,如:

# 传对象引用

以上两点在Python中让我们代码开发变得高效简洁,但是在C++中却没有这么好的特性。因此就引入C++ functional库中的reference_wrapper来让C++也能像Python这样简单地进行对象引用以及对象引用的替换。

在正式介绍reference_wrapper前, 先来看看C++中如何实现上述两个操作。

C++引用替换

如果我们把Python的引用替换代码直接翻译成C++,那显然是行不通的,如下:

auto

因为在C++中,对引用赋值与对原始对象赋值一样,并不会改变引用所指向对象的地址(不会把v指向的对象从object_A变成object_B),而只会改变所指向对象的值(如果能赋值成功的话,等同于object_A=object_B)。这是因为C++的引用只能被绑定一次,之后的赋值都是对对被绑定对象赋值(assign-through)。

如果想要在C++中实现引用替换的话,有多种变通方法。首先我们可以想到用lambda匿名函数来判断如何对这个引用变量赋值:

auto

这个方法虽然可以较为简单实现引用替换,但这不适用与逻辑相对复杂的情况,如先用指向object_A的v进行一系列操作,再把v指向另一个地方进行更多操作。而且也降低了代码的可读性。

当然我们也可以直接用原始的指针来实现:

auto

如果觉得原始指针在C++里不太雅观的话,也可以换成智能指针:

unique_ptr

但以上三种解决方法都无法像Python一样,用一个赋值号“=”轻松解决。因此,C++11开始,引入reference_wrapper[1]。上述过程可以写成:

reference_wrapper

这样,就可以像Python一样随意对引用的绑定对象进行替换,如果想要赋值的话,只需要加个get()即可:

v

C++传对象引用

那么我们再来看看如何传递对象引用给一个函数,这也是reference_wrapper提出的初衷。

同样的,我们把Python的传对象引用翻译成C++代码。首先,我们先定义一个函数,这个函数可以根据接受任意支持“++”操作的类型,对变量进行自增1:

template

C++想要让函数对一个对象进行操作,而不是对值操作后返回新的值,可以试用指针也可以试用引用,由于指针还需要进行解引用,也不符合Python代码风格,所以这里只考虑引用的情况。

此处如果我们不显示指定类型T,而让编译器去自动判断的话,如下例子会导致错误的结果:

int

这里并没有如我们预期的那样,输出自增后的结果11,而是原封不动输出10。这是因为编译器在对函数参数类型进行推断(argument deduction)的时候,不会自动推断引用类型,而是采用其原本的类型,除非我们显示指定它是个引用类型:

int

这样就会输出正确结果了。但是每次都要显示指定类型,就会使得代码变得很复杂。那么我们可以将其转化为reference_wrapper对象(ref和cref会返回reference_wrapper对象):

change

如此一来,我们就可以像Python一样,简洁地完成传对象引用的操作。

除此之外,reference_wrapper在传递构造函数参数(constructor argument forwarding)时更能凸显用处:

class 

以上有两次引用操作,但因为参数推断的时候并不会显示向模板传递引用类型,导致Wrapper类初始化时错误。而reference_wrapper可以隐式转化为T&类型,能在不改变引用类型的情况下,进行模板类型推断。

reference_wrapper介绍

现在开始正式介绍reference_wrapper。

reference_wrapper的提出初衷是为了给引用提供一个可复制构造(copy constructible)和可赋值(assignable)的包装,它有个模板类reference_wrapper和两个返回reference_wrapper对象的函数ref和cref。旨在模板函数类型推断中,引用类型可以完整地被传递下去。

同时,它也希望达成一些普通引用无法做到的应用,如可以被存在容器中和可以用做函数引用并且可以直接被调用。举例如下:

// 存在容器中

reference_wrapper应用场景[2]

1、用于模板函数/构造函数,以避免显式指定类型/实例化临时对象

在C++11前声明模板函数类型的时候,如果想用到引用,就没有办法使用auto来自动类型推断,需要显示声明变量再初始化,而用了reference_wrapper则可以直接初始化让auto来做类型推断。

同时,如果初始化的引用对象是一个函数的返回值,通常情况下在初始化的时候该返回值的生命周期已经到了,没有办法进行引用,用reference_wrapper也可以规避这个问题。

举两例如下:

// 避免显式指定类型

2、用于容器存储

不像原始引用操作,reference_wrapper是一个对象,满足STL中容器对元素的要求(尤其是可擦除,erasable),因此reference_wrapper可以作为任意容器的元素类型。

vector

3、传对象引用

这点之前介绍过,它的优势在使用C++ thread的时候更为明显,因为thread的参数只能复制构造,如果需要thread对某个参数进行修改的话,即便用reference也会报错,如:

void 

4、使含有引用成员的类可赋值/复制

假设一个函数使用一个引用变量初始化,并且将这个引用变量存在一个引用成员中,那么这个函数将无法被赋值,也无法进行复制,举例如下:

class 

如果要解决这个问题,C++11之前只能避免使用引用作为类成员,而使用指针,但有了reference_wrapper即可避免这个问题:

class 

5、传递可调用的函数对象引用

reference_wrapper可以像函数一样直接被调用,可以用来在STL算法中作为参数传递:

class 

其他注意点

1、可以自动推断左值的const类型

const 

2、只能与左值绑定

reference_wrapper 

3、可以绑定不完整类

// 以下C++20开始可支持
class A;
void regular_reference(A& x);
void wrapper_reference(reference_wrapper<A> x);

4、可以对被绑定对象赋值[3]

这里举个稍微复杂的例子,假设我们要对链表进行初始化,每一行作为一个元素:

class 

总结

reference_wrapper是C++11后一个很方便的特性,它可以使得引用能够重新绑定,同时又能显式指定是否对引用对象进行修改,还可以作为一个更强大的函数指针。实践过程中,它使得对于对象的直接操作变得简单很多,让C++开发过程中重复代码(尤其是类型推断)大大减少。

参考

  1. ^http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1436.html#class.std.reference_wrapper
  2. ^https://www.nextptr.com/tutorial/ta1441164581/stdref-and-stdreference_wrapper-common-use-cases
  3. ^https://www.youtube.com/watch?v=EKJMZCL00Ak&ab_channel=CppCon
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值