当前位置:网站首页>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");
}
边栏推荐
猜你喜欢
Cannot read properties of null (reading 'insertBefore')
DP4398:国产兼容替代CS4398立体声24位/192kHz音频解码芯片
7.16 Day22---MYSQL(Dao模式封装JDBC)
关于C#的反射,你真的运用自如嘛?
C language -- operator details
Performance testing with Loadrunner
Programming hodgepodge (4)
(Kettle) pdi-ce-8.2 连接MySQL8.x数据库时驱动问题之终极探讨及解决方法分析
《看见新力量》第四期免费下载!走进十五位科技创业者的精彩故事
音视频相关基础知识与FFmpeg介绍
随机推荐
OpenGLES 学习之帧缓存
Wwise入门和实战
9. Dynamic SQL
Unity动画生成工具
What is the salary of a software testing student?
力扣:746. 使用最小花费爬楼梯
7.16 Day22---MYSQL(Dao模式封装JDBC)
LCP 17. Quick Calculation Robot
Camera2 闪光灯梳理
3面头条,花7天整理了面试题和学习笔记,已正式入职半个月
Landing, the IFC, GFC, FFC concept, layout rules, forming method, use is analysed
Can‘t connect to MySQL server on ‘localhost3306‘ (10061) 简洁明了的解决方法
PHP实现异步执行程序
显式调用类的构造函数(而不是用赋值构造),实现一个new操作
注意!软件供应链安全挑战持续升级
8、自定义映射resultMap
php实现telnet访问端口
梳理CamStyle、PTGAN、SPGAN、StarGAN
warning C4251: “std::vector&lt;_Ty&gt;”需要有 dll 接口由 class“Test”的客户端使用错误
Tactile intelligent sharing - SSD20X realizes upgrade display progress bar