当前位置: 首页 > 工具软件 > $.bind > 使用案例 >

bind和function

姬欣怡
2023-12-01

概述

bindfunction是C ++ 11引入的非常重要的两个语言特性,通过它们C++也可以很方便的实现类似C#的中委托,从而提供了一种新的回调实现机制。先看下它们的用法。

可调用对象

在C++中函数,函数指针,指向类的成员函数指针,函数对象(重载了调用运算符的类),lambda

  1. 函数
int func(int a,int b)
{
    return a+b;
}

int c = func(10,8);

2.函数指针

int (*AddFunc)(int a,int b);
AddFunc = func;
AddFunc();

3.函数对象(重载了调用运算符的类)

struct CAdd
{
    int operator()(int a,int b)
    {
        ...
    }
}

CAdd add;
int r = add(10,8);
  1. lambda表达式(匿名函数)

可调用对象的类型

  • 可调用对象都是有类型的,很典型的如函数对象,它本质就一个重载了调用运算符的类,不同的类当然类型也不相同。

  • 函数和函数指针,它们的类型是由其返回值类型和实参类型决定,int func(int a,int b)void func()就不是同一个类型,它们对应的指针也是不同的类型。

  • 很显然函数对象与函数,函数指针也不是同一类型

调用形式

调用形式指明了调用返回的类型及传递给调用的实参类型,如下,就表示一种调用类型。

int (int,int)

不同的可调用对象可能会有相同的调用形式。

//函数
int add(int a,int b);
//函数对象
struct divide
{
    int operator()(int a,int b)
    {
        ...
    }
};

那么上述的函数add和函数对象divide的调用形式都是int (int,int),尽管它们的调用形式都一样,但是它们不是同一个类型,我们可以通过函数指针指向add,但是不能指向divide函数对象

std::map<std::string,int(*)(int,int)> functions;
//我们可以将函数add保存在functions中,却不能将divide放入map
functions.insert("add",add);

function的用法

同一种调用形式的可调用对象的类型可能不同,可以通过function将其统一起来,function是一个模板,当创建一个具体的function类型时必须提供该function类型能够表示的对象的调用信息。

function<int(int,int)>

以上代码声明了一个function类型,它可以表示接受两个int,返回一个int的可调用对象,使用示例

#include <iostream>
#include <functional>

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

struct CAdd
{
  int operator()(int a,int b)
  {
    return a + b;
  }
};

int main()
{
  //定义一个function的变量f,包装函数add
  std::function<int(int,int)> f = add;

  //函数对象
  CAdd a;
  std::cout<<a(1,2)<<std::endl;

  //包装函数对象CAdd
  std::function<int(int,int)> f1 = CAdd();
  std::cout<<f(3,4)<<std::endl;

  //函数指针
  int (*fp)(int,int) = add;
  std::cout<<fp(5,6)<<std::endl;

  //包装函数指针fp
  std::function<int(int,int)> f2 = fp;
  std::cout<<f2(7,8)<<std::endl;
  
  std::function<int(int,int)> f3;
  //判断f3是否为空
  if (!f3)
  {
    std::cout<<"f3 can't called "<<std::endl;
  }
}

function可以包装同调用形式的任意可调用对象,可以通过这样一个function类型即指向add也可以指向函数对象divide

std::map<std::string,std::function<int(int,int)> functions;
functions.insert("add",add);
functions.insert("divide",divide);

示例代码

#include <functional>
#include <iostream>
#include <map>
int add(int a,int b)
{
    std::cout<<"add function"<<std::endl;
    return a+b;
}

struct divide
{
    int operator()(int a,int b)
    {
        std::cout<<"divide function"<<std::endl;
        return a/b;
    }
};

int main()
{
    std::map<std::string,std::function<int(int,int)> functionMap;
    functionMap.insert("add",add);
    functionMap.insert("divide",divide());
    
    functionMap["add"](10,8);
    functionMap["divide"](36,2);
    
    std::system("pause");
}

bind的用法

它接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。

auto newCallable = bind(callable,agr_list);
  1. 将多元(参数个数为n,n>1)可调用对象转成一元或(n-1)元可调用对象
#include <functional>
#include <iostream>
int add(int a, int b)
{
    return a + b;
}

int main()
{
    /*std::placeholders::_1为占位符,第一个形参的值为8
    *auto add8 相当于std::function<int(int)>,可以写成
    *std::function<int(int)> = std::bind(add,std::placeholders::_1,8)
    */
    auto add8 = std::bind(add,std::placeholders::_1, 8);
    //相当于add(1,8)
    int ret = add8(1);
    std::cout << ret << std::endl;
    std::system("pause");
    
}

  1. 将对象的成员函数转换成调用对象
#include <iostream>
#include <functional>

class CTest
{
    public:
        CTest(int v):m_value(v)
        {}

        ~CTest()
        {}

    public:
        int Add(int a)
        {
            return a + m_value;
        }
    
    private:
        int m_value;
};

int main()
{
    CTest c(8);
    
    int ret1 = c.Add(0);
    //CTest的成员函数Add,第一个参数为this对应为&c
    auto f = std::bind(&CTest::Add, &c,std::placeholders::_1);
    int ret2 = f(10);
    std::cout << "ret1 " << ret1 << ",ret2 " << ret2 << std::endl;

    CTest c1(118);
    auto f2 = std::bind(&CTest::Add, &c1, std::placeholders::_1);
    int ret3 = f2(0);
    std::cout << "ret3 " << ret3 << std::endl;
    
    std::system("pause");

}

bind对象成员函数时,callable表示的成员函数的指针,第一个参数是类的对象。

  1. bind引用参数

默认情况下,bind的那些不是占位符的参数被拷贝到bind返回的可调用对象(函数对象)中。有时对有些绑定的参数我们希望以引用的方式传递,或是要绑定参数的类型无法拷贝。

#include <iostream>
#include <functional>

struct NoCopy
{
    NoCopy() = default;
    ~NoCopy() = default;
    NoCopy(const NoCopy&) = delete;
    NoCopy &operator=(const NoCopy&) = delete;

};

class CTest:public NoCopy
{
    public:
        
        CTest(int v) :m_iValue(v)
        {

        }

    public:

        int GetValue()
        {
            return m_iValue;
        }

    private:
        int m_iValue;
};

void PrintCTestValue(CTest &t)
{
    std::cout << t.GetValue() << std::endl;
}

int main()
{
    CTest t1(18);
    //编译不过,因为CTest是无法拷贝的
    //auto f = std::bind(PrintCTestValue, t1);
    auto f1 = std::bind(PrintCTestValue, std::ref(t1));
    f1();
    std::system("pause");
}

可以通过函数ref返回一个对象,包含给定的引用,此对象是可以拷贝的。还有一个cref函数,生成一个保存const引用的类。

function与bind结合

function可以存储任意类型的可调用对象,bind可以将一个可调用对象转换成新的可调用对象,那么我们可以通过bind将一个可调用对象转换成指定的funtcion类型。

#include <functional>
#include <iostream>
#include <string>

class CTest
{
    public:
        CTest(int v) :m_value(v)
        {

        }

        ~CTest()
        {

        }

    public:
        int GetValue()
        {
            return m_value;
        }

    private:
        int m_value;

};

int f1(std::string str, int a, int b)
{
    std::cout << "f1 " << "str:" << str << ",a:" << a << ",b:" << b << std::endl;
    return 0;
}

int f2(CTest &t1, int a)
{
    std::cout << "f2 " << "t1 value:" << t1.GetValue() << ",a:" << a << std::endl;
    return 0;
}

int main()
{
    CTest t(18);
    std::function<int(int, int)> ff1 = std::bind(f1, "hello", std::placeholders::_1, std::placeholders::_2);
    std::function<int(int)> ff2 = std::bind(f2, t, std::placeholders::_1);

    ff1(10, 8);
    ff2(100);

    std::system("pause");
}

新的回调实现机制

functionbind的用法是比较简单的,但是它们的特性为C++引入了一种新的回调实现机制,下面将详细描述这种思想

试想我们实现一个网络的库,对上层应用需要事件回调,有两种实现方式:

  • 面向对象的思想
  • 事件回调

面向对象的思想

通过抽象与多态来实现。首先是在库中定义几个抽象类(接口),其中声明一些(纯)虚函数,比如OnMessage(),OnDisconnect()等。使用者需要继承这些基类,并覆写这些虚函数,以获得事件回调通知,并且使用者必须把派生类对象(即handler对象,通常是动态分配的)的指针或引用注册到库中,网络库通过多态的特性来调用相应的接口(handle对象,即为事件处理)。如下伪代码:

//网络库中提供的处理事件的接口类
class NetEvent
{
    public:
        virtual void OnMessage(const std::string& strMsg) = 0;
        virtual void OnDisconnet(int i) = 0;
        virtual void Connect(int i) = 0;
};

//上层应用继承NetEvent实现各个接口
class MyEvent:pbulic NetEvent
{
    public:
        virutal void OnMessage(const std::string& strMsg)
        {
            ...
        }
        
        virtual void OnDisconnect(int i)
        {
            ...
        }
        
        virtual void Connect(int i)
        {
            ...
        }
      
};

int main()
{
    //网络库对象
    Netlib lib;
    //产生MyEvent对象
    MyEvent* pEvent = new MyEvent;
    
    lib.registerHandler(pEvent);
    lib.run();
}
  1. 对象所有权

就是对handler的生命周期的管理,首先到底谁有权释放handler对象,就需要仔细斟酌(也许可以通过智能指针来解决释放对象的问题,但是依然需要甄别所有权,对不拥用对象使用weak_ptr,拥有对象使用shared_ptr)

  1. 设计思想

库中的事件接口定义,其所要表述的思想是,当有对应的事件发生时,需要进行处理。关于这个处理事件的“东西”(在这里把回调处理函数称为“东西”)到底是什么并不关心,它可以是一个事件处理对象,可以是一个普通的函数,可以是一个对象的成员方法,或是一个函数对象。库只关心事件发生时,能有对应的“东西”去处理即可。而由于C++ 语言机制的限定,这个“东西”必须有个特定类型,在面向对象的思想中,它被定义为一个事件处理对象。在函数编程思想中,它被定义为一个函数指针。当这个“东西”有了特定的类型,也就是排它的了,即如果是一个事件处理对象,那么就不能绑定到一个函数。所以这种语言上的限制就限定了代码编写的灵活性。反观C#,python中就没有这样的限制,C#通过委托可以不用在意具体事件处理的类型,而python的函数都归为对象

  1. 代码的可维护性,可读性

继承是强耦合关系,如果通过上述的handler对象实现的方式,显然是增加了代码的耦合度,类的层次,加大了代码的复杂度,就降低了代码的可读性和可维护性

通过回调实现

回调的实现方式相对于多态会更加简明,不用引入复杂的继承类型。通过定义对应事件处理函数的指针,事件发生时,通过指针来调用事件处理函数。这是c语言的做法(函数编程)。前面提到这种方式事件处理回调的类型依然被限制。

有了bind + function后,可以通过它们来实现事件回调,一个是避免了对回调处理函数的类型限制,二是避免了引入继承类型,下面通过伪代码来演示使用方法:


#include <iostream>
#include <functional>
typedef function<void(const std::string&)> onMessage;
typedef function<void(int)> OnConnect;
typedef function<void(int)> OnDisConnect;

enum enEventType
{
    enEnvetType_Msg,
    enEnvetType_Connect,
    enEnvetType_DisConnect
};

class CNetLib
{
    public:
        NetLib()
        {}
        
        ~NetLib()
        {}
        
    public:
        int Init(const OnMessage& MsgFunc,
                 const OnConnect& ConnectFunc,
                 const OnDisConnect& DisConnectFunc)
        {
            m_fOnMsg = MsgFunc;
            m_fOnConnect = ConnectFunc;
            m_fOnDisConnect = DisConnectFunc;
        }
        
        void Process(enEventType event)
        {
            switch(event)
            {
                case enEnvetType_Msg:
                {
                    m_fOnMsg("msg comming");
                }
                case enEnvetType_Connect:
                {
                    m_fOnConnect(18);
                }
                case enEnvetType_DisConnect:
                {
                    m_fOnDisConnect(118);
                }
            }
        }
    private:
        OnMessage m_fOnMsg;
        OnConnect m_fOnConnect;
        OnDisConnect m_fOnDisConnect;
    
};

void OnMsg(const std::string& msg)
{
    std::cout<<msg<<std::endl;
}

struct SConnect
{
    void operator()(int i)
    {
        std::cout<<"connect "<<i<<std::endl;
    }
}

class CConnect
{
    public:
       CConnect(){}
       ~CConnect(){}
    public:
       void OnDisconnect(int i)
       {
           std::cout<<"disconnect "<<i<<std::endl;
       }
}

int main()
{
    CNetLib lib;
    CConnect c;
    lib.Init(OnMsg,SConnect(),std::bind(&CConect::OnDisconnect,&c,_1));
    std::system("pause");
}
 类似资料: