当前位置: 首页 > 科技观察

为什么std--function在C++中仍然需要函数指针?

时间:2023-03-20 15:00:09 科技观察

大家好,我是小风哥。在C/C++中,指针可以用来指向一段代码。该指针称为函数指针。假设有这样一段代码:#includeintfunc(inta){returna+1;}voidmain(){int(*f)(int)=func;printf("%p\n",f);}我们定义一个函数func,然后用指针变量f指向函数,然后打印出变量f指向的地址,代码很简单,然后我们编译,看看编译后生成的指令,我们关注func函数:0000000000400526:400526:55push%rbp400527:4889e5mov%rsp,%rbp40052a:897dfcmov%edi,-0x4(%rbp)40052d:8b45fcmov-0x4(%rbp),%eax400530:83c001add$0x1,%eax400533:5dpop%rbp400534:c3retq可以看到,编译好了最后函数func位于地址0x400526,让我们记住这个地址。然后运行编译好的程序,想想这段代码会输出什么?很明显应该是func函数在内存中的地址!$./a.out0x400526你猜对了,其实函数指针也是一个指针,只不过这个指针指向的不是内存中的一段数据,而是内存中的一段代码,就像这样:看,我们经常用指针一般指向内存中的一段数据,而函数指针指向内存中的一段代码。在这个例子中,它指向内存地址0x400526,这里存放着函数func的机器指令。现在你应该理解函数指针了。细心的同学可能会有疑问,为什么编译器在生成可执行文件的时候就知道函数func存放在内存地址0x400526呢?这个不是应该在程序加载到内存开始运行的时候才确定吗?函数指针的作用是将一段代码作为变量传递。主要用途之一是回调函数。回调函数可以参考这篇文章《回调函数的实现原理》。回调函数其实是在模块A中定义的,在模块B中调用的,就像这样:但是有时候我们会有这样的场景,我们还是需要在模块A中定义一个函数,而函数A的运行需要依赖数据由模块B生成,然后将模块A定义的函数和模块B生成的数据传递给C模块调用,就像这样:这时候简单的函数指针是不够的,因为函数指针只是简单的指向到记忆中。一段代码,我们不仅要传递内存中的一段代码,还要传递内存中的一段数据给模块C,此时可以定义一个结构体来封装代码和数据,像这样:typedefvoid(*func)(int);结构闭包{funcf;整数参数;};我们将这个结构体命名为闭包,注意这个结构体有两部分:指向代码的指针变量和存储数据的变量。这样,我们在模块A中为指针变量赋值,在模块B中为保存数据的变量赋值,然后将这个结构体传递给模块C,在模块C中可以这样使用:voidrun(structfunctorfunc){func->f(func->arg);}即闭包既包含一段代码,也包含这段代码使用的数据。这里的数据也叫context,即语境,或者是environment,即环境。不管怎么称呼,它实际上是一个函数操作依赖数据:而这正是C++中的std::function的作用。简单的函数指针没有捕获上下文的能力。这里的上下文是指代码所依赖的数据。你必须自己构造一个结构来存储代码所依赖的上下文。在C++中,不能简单地使用函数指针指向对象的成员函数,因为函数指针没有办法捕获this(指向对象的指针)的上下文。std::function的作用本质上和我们刚刚定义的结构没有太大区别。使用std::function,不仅可以保存一段代码,还可以保存必要的上下文,然后在合适的地方根据上下文调用这段代码。同时std::function更通用,你可以用它来存储任何可调用对象(callableobject),只要它有正确的函数签名。