function函数的用法_深入浅出C++的function

f806e5c743548b7eb1c924fa8e3a96cc.png

今天我们来聊聊C++的function。std::function是从C++11开始支持的特性,它起什么作用?又有什么好处呢?

C语言中的函数指针

对C语言熟悉的同学应该都知道,C语言中有一种高级技巧叫作函数指针,我们可以让函数指针指向参数类型相同、返回值类型也相同的函数。通过函数指针我们也可以实现C++中的多态。我们来看个例子:

#include<iostream>
typedef int (*func)();

int print1(){
    printf("hello, print1 n");
    return 0;
}

int print2(){
    printf("hello, print2 n");
    return 0;
}

int main(int argc, char * argv[]){
    func fp = print1;
    fp();

    fp = print2;
    fp();

    return 0;
}

上面代码中定义了一个函数指针func,它可以指向无输入参数,返回值为整型的函数。因此在main函数中,我们可以用fp(这是func类型的指针)分别指向print1和print2并调用它们。

其运行结果如下:

hello, print1
hello, print2

function的作用

从上面的C代码中我们可以看到C函数指针的作用,那在C++中是否也类似这样的功能呢?没错function就是完成这个任务的。但std::function比C的函数指针功能更强大些或者说更适合C++中使用。

下面我们来看一下如何在C++中使用std::function实现指向不同的函数吧。代码如下:

void print1(){
    std::cout << "hello, print1" << std::endl;
}

void print2(){
    std::cout << "hello, print2" << std::endl;
}

int main(int argc, char *argv[])
{
    std::function<void()> func(&print1);
    func();

    func = &print2;
    func();

    return 0;
}

上面代码与C函数指针一样定义了两个全局函数print1和print2。在main函数中又定义了std::function 对象 func,然后将print1和print2分别赋值给func,这样就可以达到与C语言中指针同样的功能了。

其运行结果如下:

hello, print1
hello, print2

可以看到std::function的结果与上面C函数指针的结果是一致的,因此std::function就是C++中用来代替C函数指针的。但如果std::function只是实现上面的功能也没啥好奇怪的对吧?实际上std::function还有一个特别有意思的用法,你可以将一个重载了()操作符的对象赋值给它,这样就可以像调用函数一样使用该对象了。下面咱们就对上面的代码做下简单修改,如下:

...
struct A {
    void operator()() {
        std::cout << "This is A Object" << std::endl;
    }
};
...

int main(...){
    ...

    A a;
    func = a;
    func();

    ...

}

在上面的代码中,使用struct定义了一个结构体,而在该结构体中重载了()操作符,因此只要你将A的类对象赋值给func,它就可以像函数一样使用了。其结果如下:

...
This is A Object

function的实现原理

是不是觉得function做的事儿还挺神奇的?它是如何实现的呢?下面我们就来扒一扒它是如何实现的。

从实现上来说,有两种办法可以实现std::function:一种是通过类的多态,即通过虚表来达到多态;另一种方法是通过C语言的函数指针来实现。今天我们只介绍通过类多态的方式来实现function,对于通过函数指针实现的方式你可以自己去研究一下。

现在我们由浅入深的来分解一下function。通过观察我们可以发现function是一个包装类,它可以接收普通函数、函数类对象(也就是实现了()操作符的类对象)等。它是如何做到的呢?

最简单的方式就是通过类模板。我们都知道function的类模板参数是可变的,但我们为了简单,所以只实现有一个参数的function类模板。这也符合我们的目标,只是扒一下实现原理,并不是想自己去实现它。

OK,下面我们来看看该如何定义这个类模板吧。

template<typename R, typename Arg0>
class myfunction<R(Arg0)> {
   ...
   public:
       R operator()(Arg0 arg0){
           return ...;
       }
};

上面的代码定义了一个最简单的,只有一个参数的类模板。它是 function<int(int)>function<String(int)> 等格式的类模板。这样我们在外型上与标准库中的std::function类似了。

接下来我们需要思考一下,如何让我们自己实现的function可以调用不同的函数呢?从其行为上可以推理出其内部应该有一个指针,而且这个指针具有多态性。想想C++中的多态是如何实现的?通过继承和虚表对吧。所以在function内部应该有一个基类指针,所有传入到function中的函数、类函数对象等都应该是继承于该类的子类成员。除此之外,还要在()操作符前加virtual关键字,让它创建虚表。

了解了上面的原理后,下面我们就在自己的function中增加基类及其类的指针。代码如下:

template<typename R, typename Arg0>
class myfunction<R(Arg0)> function {

    private:
        class __callbase {
            public:
                virtual R operator()(Arg0 arg0) = 0;
                virtual ~__callbase() {}
        };

        __callbase *base_;

        ...

    public:
        ...
        R operator()(Arg0 arg0){
            return (*__callbase)(arg0); //这里调用基类对象的()操作符
        }

};

上面我们就将多态中的基类实现好了,在上面的代码中最关键是的operator()中增加了virtual关键字,这样该函数就被放到了vtable中,后面就可以在子类中实现该方法了。下面我们来实现子类。

...
class myfunction<R(Arg0)> function{
    private:
        ...

        template<typename F>
        class __callable: public __callbase {

            public:
                callable(F functor)
                    : functor(functor){}

                virtual R operator()(Arg0 arg0) {
                    return functor(arg0);
                }

            private:
                F functor;
        };

        ...
    public:
        ...
        template<typename F>
        myfunction(F f): base_(new __callable<F>(f)){
        }

        ~myfunction(){
            if(base_) {
                delete base_;
                base_ = nullptr;
            }
        }
};

在子类的实现中,核心点是增加指向赋值给function类的函数指针或函数类对象,也就是上面__callable类中的F functor 成员。该成员的类型是通过模板template<typename F>推导出来的。如果我们在创建function时传入的是函数,那么functor就是一个函数指针,如果我们传入的是函数类对象,则functor就是类对象。

另外你可以发现,我分别在myfunction类的构造函数和__callable类前定义了模板F,这样当我们在main函数中创建myfunction对象时,通过类型推导就可以获到F的具体类型了。代码如下:

int print(int a){
    ...
    return 0;
}

int main(...){
    ...
    myfunction myfunc(print); //通过这句可以获得F类型为函数指针
}

有了functor成员后,还需要在构造__callable时给functor赋值,也就是让functor指向具体的函数或函数类对象。之后重载()操作符就可以直接调用具体的函数或函数类对象了。

通过以上讲解我想你应该已经知道标准库中的function实现的基本原理了。当然我们这里实现的比较简陋,真正的实现还要考虑很多性能的问题,所以实现的要比这个复杂得多。另外标准库中的实现是通过函数指针来实现的而非通过C++的多态。

不过我们今天实现的的myfunction虽然与标准库有很多不同,但原理都是类似的,对于我们理解function已经足够了。

小结

在本文中我首先向你介绍了std::function的作用以及如何使用它,之后又苞丁解牛的实现了一个最简陋的function,主要的目的是加深你对function的理解。

参考

C++高阶知识:深入分析移动构造函数及其原理​avdancedu.com 聊聊C++中的完美转发​avdancedu.com 细说智能指针​avdancedu.com 聊聊C++中的类型转换​avdancedu 重学C/C++中的const​avdancedu.com
997eca2485bfdfc221879a7fc07325b3.png
  • 8
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值