zoukankan      html  css  js  c++  java
  • C++11中的std::bind和std::function

    C++11中的std::bind和std::function

    可调用对象

    • 是一个函数指针
    • 一个类成员函数指针
    • 可被转换成函数指针的类对象
    • 是一个具有operator()成员函数的类的对象

    std::bind

    std::bind可以理解为“绑定”

    绑定普通函数

    int AddFunc(int a, int, b) { return a + b; }
    auto FuncBind = std::bind(AddFunc, 2, 3);	//AddFunc隐式转换为一个函数指针
    std::cout << FuncBind() << std::endl;		// 5
    

    函数AddFunc被绑定到了FuncBind,而它的参数绑定到一个具体的值上,这个例子中绑定到了2和3,因此在函数调用的时候程序给出的结果是5

    参数还可以绑定到占位符(std::placeholders)上,在调用FuncBind时再给出其参数值

    auto FuncBind = std::bind(AddFunc, std::placeholders::_1, 3);
    std::cout << FuncBind(5) << std::endl;		// 8
    

    占位符,顾名思义,它替“5”占了一个 位子,后续调用时,“5”来了,坐了别人给他占的座位。你当然也可以给“3”提供一个占位符

    std::bind(AddFunc, std::placeholders::_1, std::placeholders::_2);
    FuncBind(6,9);		//15
    

    C++提供了多达20个占位符(_1, _20)可供使用。什么?嫌太少?你见过有函数一次性调用二三十个参数的吗?

    绑定成员函数

    1. 第一个参数为对象的成员函数指针,且由于其不支持类成员函数隐式转换为函数指针,所以需要手动加上取值符&
    2. 使用对象成员函数的指针时,需要指定该指针属于那个对象。所以需要创建出一个对象实例并将它的地址当作参数
    class MyClass {
    public:
        int Sum(int a, int b) { return a + b; }
    };
    MyClass myc;
    auto FuncBind = std::bind(&MyClass::Sum, &myc, 10, 20);
    //调用FuncBind的结果为:30
    

    绑定引用参数

    std::ref用于包装按引用传递的值

    std::cref用于包装按常量引用传递的值

    int Sum(int& a, const int& b) { return a++ + b; }
    int a = 1, b = 2;
    auto FuncBind = std::bind(Sum, std::ref(a), std::cref(b));		//运行后,a = 2, b = 2, 结果为3
    

    总结

    1. std::bind预先绑定的参数需要传递具体变量或者值进去,同时此过程是pass-by-value,如果想以pass-by-reference的形式进行传递,则需要使用std::ref或是std::cref
    2. 若不想预先传值,则需要传入占位符,从std::placeholders_1开始,逐步递增,此过程为pass-by-reference

    std::function

    封装函数

    std::function是一种通用,多态的函数封装。可容纳各种可调用对象,例如普通函数,函数指针,Lambda表达式以及std::bind表达式等。换句话说,可以当作是函数的容器。对于类型不安全的函数指针来说,将其封装成一个安全的std::function对象是一个良好的选择

    假设有一个简单的普通函数

    int AddFunc(int a, int, b) { return a + b; }
    

    利用std::function进行封装

    int (*MyFuncP)(int, int) = AddFunc;					//使用函数指针
    std::function<int(int, int)> MyFunc = AddFunc;			//利用std::function进行封装
    

    与std::bind配合使用

    像是封装函数对象(其实就是operator()),Lambda表达式等其他可调用对象的过程与上述类似,不再赘述,重点关注std::functionstd::bind搭配使用

    class MyClass {
    public:
        int Sum(int a, int b) { return a + b; }
        int num;
    };
    
    using namespace std::placeholders;
    MyClass myc;
    std::function<int(int, int)> MyFunc = std::bind(&MyClass::Sum, &myc, _1, _2);	//绑定成员函数
    std::function<int&()> MyValue = std::bind(&MyClass::num, &myc);					//绑定成员变量
    MyValue() = 100;			//等效于 myc.num = 100;
    

    与函数指针对比

    使用std::function+std::bind

    class Base
    {
    public:
        void Called() {
            for (int i = 0; i < myVec.size(); i++)
                myVec[i]();
        }
    protected:
        std::vector<std::function<void()>> myVec;
    };
    class Devired : public Base
    {
    public:
        Devired()
        {
            std::function<void()> st = std::bind(&Devired::DoSomething, this);
            std::function<void()> et = std::bind(&Devired::DoEverything, this);
            myVec.emplace_back(st);
            myVec.emplace_back(et);
        }
    private:
        void DoSomething() { std::cout << "Some
    "; }
        void DoEverything() { std::cout << "Every
    "; }
    };
    

    在主函数中通过基类中的函数以及成员调用到派生类的方法

    Devired dr;
    dr.Called();		//控制台输出 Some Every
    

    使用函数指针

    class BaseP
    {
    public:
        void Called() {
            for (int i = 0; i < myVec.size(); i++)
                myVec[i]();
        }
    protected:
        std::vector<void(*)()> myVec;
    };
    

    在基类中将创建一个装 void返回值的无参函数指针 的容器,然后在派生类的构造函数中将派生类成员函数加进容器中

    //省略其余部分
    DeviredP()
    {
        auto st = &DeviredP::DoSomething;
        auto et = &DeviredP::DoEverything;
        myVec.emplace_back(st);
        myVec.emplace_back(et);
    }
    

    这种写法是错误的,调用drp.Called()时会报错

    派生类

    class BaseP { virtual void Called() = 0; };
    class DeviredP : public BaseP
    {
    public:
        DeviredP()
        {
            auto st = &DeviredP::DoSomething;
            auto et = &DeviredP::DoEverything;
            myVec.emplace_back(st);
            myVec.emplace_back(et);
        }
        void Called() override
        {
            for (int i = 0; i < myVec.size(); i++)
                (this->*myVec[i])();				//需要用this指针指明
        }
    private:
        std::vector<void(DeviredP::*)()> myVec;			//需指定类名:储存的是DeviredP类的成员函数指针
        void DoSomething() { std::cout << "Some
    "; }
        void DoEverything() { std::cout << "Every
    "; }
    };
    

    不仅储存指针的时候变繁琐了,调用时还应使用this显示指明。而且还需要在每个不同的派生类间去书写相同的代码,项目变得冗长。

    使用函数指针的版本无法在基类中取到派生类的函数指针,调用逻辑也需要在子类中去实现。而使用std::function,函数都被统一归为function函数对象,方便基类调用

    漫漫谈

    虚函数与std::function

    虚函数的方法实现责任链模式

    struct Request { int RequestType; };
    class Handler
    {
    public:
        void setNext(std::shared_ptr<Handler> shrd) { nextHandler = std::move(shrd); }
        virtual void HandlerRequest(Request rq)
        {
            if (nextHandler)
                nextHandler->HandlerRequest(rq);
            else
                std::cout << "Cant Handle
    ";
        }
    protected:
        std::shared_ptr<Handler> nextHandler;
    };
    
    class DeviredHandler1 : public Handler
    {
    public:
        void HandlerRequest(Request rq) override
        {
            if (rq.RequestType == 1)
                std::cout << "Handle by 1
    ";
            else
                Handler::HandlerRequest(rq);
        }
    };
    // DeviredHandler2,DeviredHandler3...
    
    Request r = { 3 };
    auto d1 = std::make_shared<DeviredHandler1>();
    auto d2 = std::make_shared<DeviredHandler2>();
    auto d3 = std::make_shared<DeviredHandler3>();
    d1->setNext(d2);
    d2->setNext(d3);
    d1->HandlerRequest(r);		// Handle by 3
    

    std::bind + std::function方式实现责任链模式

    在原有的基础上增加ChainHandler

    using MyFunc = std::function<void(Request)>;
    class ChainHandler
    {
    public:
        MyFunc Myfunc;
        void HandleRequest(Request rq) { Myfunc(rq); }
        void Assemble(MyFunc call, MyFunc next, Request rq)
        {
            if (next != nullptr)
                next(rq);
            else
                call(rq);
        }
    };
    
    //main函数中不再使用 d1->HandlerRequest(r);
    ChainHandler chain;
    MyFunc m1 = std::bind(&DeviredHandler1::HandlerRequest, d1, std::placeholders::_1);
    MyFunc m2 = std::bind(&DeviredHandler2::HandlerRequest, d2, std::placeholders::_1);
    MyFunc m3 = std::bind(&DeviredHandler3::HandlerRequest, d3, std::placeholders::_1);
    chain.Myfunc = std::bind(&ChainHandler::Assemble, &chain, m1, chain.Myfunc, std::placeholders::_1);
    chain.Myfunc = std::bind(&ChainHandler::Assemble, &chain, m2, chain.Myfunc, std::placeholders::_1);
    chain.Myfunc = std::bind(&ChainHandler::Assemble, &chain, m3, chain.Myfunc, std::placeholders::_1);
    chain.HandleRequest(r);		// Handle by 3
    

    使用std::function创建一个自动注册工厂

    摸了,下次一定

    C++11实现一个自动注册的工厂

    一个更好的自动注册工厂

    再谈自动注册的工厂

  • 相关阅读:
    十四
    十三
    十二
    十一
    用Linq从一个集合选取几列得到一个新的集合-可改列名
    LINQ入门(完结篇)
    LINQ入门(下篇)
    LINQ入门(中篇)
    LINQ入门(上篇)
    MVC中View往Controllers传数据的方式-已发
  • 原文地址:https://www.cnblogs.com/tuapu/p/14167159.html
Copyright © 2011-2022 走看看