bind
和function
是C ++ 11引入的非常重要的两个语言特性,通过它们C++也可以很方便的实现类似C#的中委托,从而提供了一种新的回调实现机制。先看下它们的用法。
在C++中函数,函数指针,指向类的成员函数指针,函数对象(重载了调用运算符的类),lambda
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);
可调用对象都是有类型的,很典型的如函数对象
,它本质就一个重载了调用运算符的类,不同的类当然类型也不相同。
函数和函数指针,它们的类型是由其返回值类型和实参类型决定,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<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");
}
它接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。
auto newCallable = bind(callable,agr_list);
#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");
}
#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表示的成员函数的指针,第一个参数是类的对象。
默认情况下,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可以将一个可调用对象转换成新的可调用对象,那么我们可以通过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");
}
function
与bind
的用法是比较简单的,但是它们的特性为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();
}
就是对handler的生命周期的管理,首先到底谁有权释放handler对象,就需要仔细斟酌(也许可以通过智能指针来解决释放对象的问题,但是依然需要甄别所有权,对不拥用对象使用weak_ptr,拥有对象使用shared_ptr)
库中的事件接口定义,其所要表述的思想是,当有对应的事件发生时,需要进行处理。关于这个处理事件的“东西”(在这里把回调处理函数称为“东西”)到底是什么并不关心,它可以是一个事件处理对象,可以是一个普通的函数,可以是一个对象的成员方法,或是一个函数对象。库只关心事件发生时,能有对应的“东西”去处理即可。而由于C++ 语言机制的限定,这个“东西”必须有个特定类型,在面向对象的思想中,它被定义为一个事件处理对象。在函数编程思想中,它被定义为一个函数指针。当这个“东西”有了特定的类型,也就是排它的了,即如果是一个事件处理对象,那么就不能绑定到一个函数。所以这种语言上的限制就限定了代码编写的灵活性。反观C#,python中就没有这样的限制,C#通过委托可以不用在意具体事件处理的类型,而python的函数都归为对象。
继承是强耦合关系,如果通过上述的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");
}