今天我们来聊聊C++的function。std::function是从C++11开始支持的特性,它起什么作用?又有什么好处呢?
C语言中的函数指针
对C语言熟悉的同学应该都知道,C语言中有一种高级技巧叫作函数指针,我们可以让函数指针指向参数类型相同、返回值类型也相同的函数。通过函数指针我们也可以实现C++中的多态。我们来看个例子:
1 |
|
上面代码中定义了一个函数指针func
,它可以指向无输入参数,返回值为整型的函数。因此在main函数中,我们可以用fp(这是func类型的指针)分别指向print1和print2并调用它们。
其运行结果如下:
1 | hello, print1 |
function的作用
从上面的C代码中我们可以看到C函数指针的作用,那在C++中是否也类似这样的功能呢?没错function就是完成这个任务的。但std::function比C的函数指针功能更强大些或者说更适合C++中使用。
下面我们来看一下如何在C++中使用std::function实现指向不同的函数吧。代码如下:
1 | void print1(){ |
上面代码与C函数指针一样定义了两个全局函数print1和print2。在main函数中又定义了std::function 对象 func,然后将print1和print2分别赋值给func,这样就可以达到与C语言中指针同样的功能了。
其运行结果如下:
1 | hello, print1 |
可以看到std::function的结果与上面C函数指针的结果是一致的,因此std::function就是C++中用来代替C函数指针的。但如果std::function只是实现上面的功能也没啥好奇怪的对吧?实际上std::function还有一个特别有意思的用法,你可以将一个重载了**()**操作符的对象赋值给它,这样就可以像调用函数一样使用该对象了。下面咱们就对上面的代码做下简单修改,如下:
1 | ... |
在上面的代码中,使用struct
定义了一个结构体,而在该结构体中重载了**()**操作符,因此只要你将A的类对象赋值给func,它就可以像函数一样使用了。其结果如下:
1 | ... |
function的实现原理
是不是觉得function做的事儿还挺神奇的?它是如何实现的呢?下面我们就来扒一扒它是如何实现的。
从实现上来说,有两种办法可以实现std::function:一种是通过类的多态,即通过虚表来达到多态;另一种方法是通过C语言的函数指针来实现。今天我们只介绍通过类多态的方式来实现function,对于通过函数指针实现的方式你可以自己去研究一下。
现在我们由浅入深的来分解一下function。通过观察我们可以发现function是一个包装类,它可以接收普通函数、函数类对象(也就是实现了()操作符的类对象)等。它是如何做到的呢?
最简单的方式就是通过类模板。我们都知道function的类模板参数是可变的,但我们为了简单,所以只实现有一个参数的function类模板。这也符合我们的目标,只是扒一下实现原理,并不是想自己去实现它。
OK,下面我们来看看该如何定义这个类模板吧。
1 | template<typename R, typename Arg0> |
上面的代码定义了一个最简单的,只有一个参数的类模板。它是 function<int(int)> 或 function<String(int)> 等格式的类模板。这样我们在外型上与标准库中的std::function类似了。
接下来我们需要思考一下,如何让我们自己实现的function可以调用不同的函数呢?从其行为上可以推理出其内部应该有一个指针,而且这个指针具有多态性。想想C++中的多态是如何实现的?通过继承和虚表对吧。所以在function内部应该有一个基类指针,所有传入到function中的函数、类函数对象等都应该是继承于该类的子类成员。除此之外,还要在**()**操作符前加virtual
关键字,让它创建虚表。
了解了上面的原理后,下面我们就在自己的function中增加基类及其类的指针。代码如下:
1 | template<typename R, typename Arg0> |
上面我们就将多态中的基类实现好了,在上面的代码中最关键是的operator()
中增加了virtual关键字,这样该函数就被放到了vtable中,后面就可以在子类中实现该方法了。下面我们来实现子类。
1 | ... |
在子类的实现中,核心点是增加指向赋值给function类的函数指针或函数类对象,也就是上面__callable
类中的F functor 成员。该成员的类型是通过模板template<typename F>
推导出来的。如果我们在创建function时传入的是函数,那么functor就是一个函数指针,如果我们传入的是函数类对象,则functor就是类对象。
另外你可以发现,我分别在myfunction类的构造函数和__callable
类前定义了模板F
,这样当我们在main函数中创建myfunction对象时,通过类型推导就可以获到F的具体类型了。代码如下:
1 | int print(int a){ |
有了functor成员后,还需要在构造__callable
时给functor赋值,也就是让functor指向具体的函数或函数类对象。之后重载**()**操作符就可以直接调用具体的函数或函数类对象了。
通过以上讲解我想你应该已经知道标准库中的function实现的基本原理了。当然我们这里实现的比较简陋,真正的实现还要考虑很多性能的问题,所以实现的要比这个复杂得多。另外标准库中的实现是通过函数指针来实现的而非通过C++的多态。
不过我们今天实现的的myfunction虽然与标准库有很多不同,但原理都是类似的,对于我们理解function已经足够了。
小结
在本文中我首先向你介绍了std::function的作用以及如何使用它,之后又苞丁解牛的实现了一个最简陋的function,主要的目的是加深你对function的理解。
参考
C++高阶知识:深入分析移动构造函数及其原理
聊聊C++中的完美转发
细说智能指针
聊聊C++中的类型转换
重学C/C++中的const