当前位置:网站首页>bind和function
bind和function
2022-08-04 05:25:00 【mo4776】
概述
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);
- 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);
- 将多元(参数个数为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");
}
- 将对象的成员函数转换成调用对象
#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的那些不是占位符的参数被拷贝到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");
}
新的回调实现机制
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");
}
边栏推荐
猜你喜欢
The symbol table
7.15 Day21---MySQL----Index
MySQL database (basic)
Cannot read properties of null (reading ‘insertBefore‘)
Swoole学习(一)
Do you think border-radius is just rounded corners?【Various angles】
C1认证之web基础知识及习题——我的学习笔记
idea设置识别.sql文件类型以及其他文件类型
部署LVS-DR群集【实验】
Can 't connect to MySQL server on' localhost3306 '(10061) simple solutions
随机推荐
7.18 Day23 - the markup language
Swoole学习(一)
实现登录密码混合动态因子,且动态因子隐式
企业需要知道的5个 IAM 最佳实践
FFmpeg源码分析:avformat_open_input
C语言 -- 操作符详解
腾讯136道高级岗面试题:多线程+算法+Redis+JVM
7.16 Day22---MYSQL (Dao mode encapsulates JDBC)
关于C#的反射,你真的运用自如嘛?
JS基础--强制类型转换(易错点,自用)
手把手教你实现buffer(二)——内存管理及移动语义
8.03 Day34---BaseMapper查询语句用法
The cost of automated testing is high and the effect is poor, so what is the significance of automated testing?
npm报错Beginning October 4, 2021, all connections to the npm registry - including for package installa
解决安装nbextensions后使用Jupyter Notebook时出现template_paths相关错误的问题
程序员也应了解的Unity粒子系统
MySQL database (basic)
4.3 基于注解的声明式事务和基于XML的声明式事务
嵌入式系统驱动初级【3】——字符设备驱动基础中_IO模型
显式调用类的构造函数(而不是用赋值构造),实现一个new操作