C++类成员函数作回调函数
前面写了一篇文章 C语言消息注册派发模式 介绍了下我理解的C语言消息派发。因为C语言是函数式语言,写回调函数的时候很简单 参数就是一个简单的函数指针就好了, 那在C++里的时候 就有些不一样了,虽然C++中我们也可以写普通函数,但是C++是面向对象的语言,所以显而易见的问题就来了, 如何将类的非静态成员函数作回调函数就是我这里想分享的。
为了更清晰和快速的向大家讲明白相关知识点,这里将分为:前提基础知识、简略讲解、实例运用 三部分进行讲解。让大家快速汲取到自己所想要了解的点。
第一部分 前提基础知识
C++版本:C++11
头文件:#include <functional>
名称空间:
std; C++标准命名空间
std::placeholders; 包含std::bind()函数占位参数的名称空间
特性:
auto 占位类型说明符
std::function 提供存储任意类型函数对象的支持。
std::bind() 绑定一或多个实参到函数对象
特性详情请见cppreference原文:
英文版:Function objects
中文版:函数对象
函数指针基础知识(可以参考C语言消息注册派发模式及回调函数简介)
第二部分 简略讲解
std::function用法:
在C语言中的时候 回调函数的格式是一个函数指针如
void (*pfunc)(int);
定义了一个返回值为void, 参数为一个int 的函数指针,用法如下代码片段:
void print(int id)
{std::cout << "我的编号为:" << id << endl;
}void (*pfunc)(int) = print; //将函数赋值给pFunc类型的函数指针
pfunc(1); //调用的结果和print(1)结果一致
//输出:我的编号为:1
如果我们用std::function来实现同样的需求:
std::function<void(int)> pfunc;
这里同样是定义了一个返回值为void, 参数为一个int的函数指针,相比上面C式的函数指针来说 std::function<返回值类型(函数参数类型列表)> 函数指针变量名; 这样的写法会更加清晰,更符合一贯定义变量的语法习惯,实现同样功能的代码如下:
void print(int id)
{std::cout << "我的编号为:" << id << endl;
}std::function<void(int)> pfunc = print; //将函数赋值给pFunc类型的函数指针
pfunc(1); //调用的结果和print(1)结果一致
//输出:我的编号为:1
std::bind()用法:
绑定一个函数参数返回一个函数对象
参数1:可调用的函数对象(函数对象、指向函数指针、到函数引用、指向成员函数指针或指向数据成员指针)
参数2:要绑定的参数列表,未绑定参数为命名空间 std::placeholders
的占位符 _1, _2, _3...
所替换
我们需要的用类成员函数作回调函数主要就是通过bind实现的,在成员函数之前我们先看下实现上面代码功能的代码片段:
void print(int id)
{std::cout << "我的编号为:" << id << endl;
}std::function<void(int)> pfunc = std::bind(print, std::placeholders::_1);
pfunc(1);
//输出:我的编号为:1
这段代码功能和前面的代码结果表现一致,参数print就是可调用的函数对象,参数std::placeholders::_1就是一个占位符,简单理解就是将来调用该函数的时候我们不知道传过来的参数是什么值,只知道会有一个参数传过来,所以这里先占个坑,俗语说占着_坑,_ _ _,就是这意思,先把坑占了,后面调用的时候传入的参数就会填上这个坑,我们也可以先给绑定一个固定的参数,也是可以的,比如:
void print(int id)
{std::cout << "我的编号为:" << id << endl;
}std::function<void(int)> pfunc = std::bind(print, 8888);
pfunc(0);
//输出:我的编号为:8888
这里为什么调用的时候传入的是0,但是实际输出却是8888,因为我们std::bind的时候将第一个参数给绑定成了8888,所以在调用的时候print函数第一个参数被传入的就是8888,那我们调用pfunc的时候传入的参数是被忽略了,可能你要说了,既然忽略了 那为什么还要传入呢,这是因为我们接收std::bind返回值的时候使用的是std::function<void(int)> pfunc函数指针,该函数指针要求我们必须要输入一个参数,如果不给参数的话,就会报 “不会接受0个参数的函数“ 的错误。
实际上如果我们用占位类型说明符auto 来自动推导std::bind返回值的类型,那么我们就可以在调用的时候不传参数。
void print(int id)
{std::cout << "我的编号为:" << id << endl;
}auto pfunc = std::bind(print, 8888);
pfunc();
//输出:我的编号为:8888
那么重点来了,下面我们先看下将类的成员函数封装成函数对象并调用的代码:
#include <iostream>
#include <functional>class TestA {
public://非静态成员函数void print(int id) {std::cout << "我的编号为:" << id << std::endl;}//静态成员函数static void print2(int id) {std::cout << "我的静态编号为:" << id << std::endl;}
};int main(void){//非静态成员函数绑定,必须要先定义一个函数对象,再进行绑定TestA a;auto func = std::bind(&TestA::print, &a, std::placeholders::_1);func(1);//静态成员函数绑定,不需要定义对象auto func2 = std::bind(&TestA::print2, std::placeholders::_1);func2(1);return 0;
}
非静态成员函数绑定
std::bind()第一个参数是 &TestA::print, 因为TestA::print表示类TestA的函数print,我们需要传入函数地址,所以加上取地址符&,也就是传入指向类成员函数print的指针了。
第二个参数是被绑定的对象的指针,在返回的函数对象被调用的时候,会根据这个对象指针,调用对应的成员函数。因为调用类的非静态成员函数必须有相应的对象才可以被调用,如果直接TestA::print(1)是错误的
第三个参数是占位符
静态成员函数绑定
类的静态成员函数,因为调用的时候是 类名::函数名 如TestA::print2(1);的形式,和普通C函数一致,所以绑定的方法就和普通C函数类似,不需要先定义对象。
std::bind还有更深入的用法,具体大家可以查看cppreference,现在我们知道成员函数是怎样绑定为函数对象,那作回调函数就只需要按照正常参数传递就好了,下面进入实例运用
第三部分 实例运用
假定现在要做一个游戏,游戏中有很多不同的功能也可以称系统,比如背包系统、采集系统、任务系统等等,每个系统都是一个类,这些系统类是跟玩家相关的,每个玩家都会持有这些系统类对象,每个系统类都有各自的成员方法来对系统数据进行操作。
比如背包系统,现在玩家要删除一个道具的时候,是客户端发送协议(玩家id、协议号、协议数据等)到服务器,然后服务器收到协议后根据玩家id找到对应的玩家,并根据协议号找到该玩家持有的背包系统的 对应的 客户端删除道具逻辑 的成员函数,传入客户端发来的协议数据然后进行调用,完成删除一个道具的操作。
我们这个实例就来实现下如何根据协议号来调用玩家对应的系统类对应逻辑的成员函数,话不多说,直接上代码,如果有需要探讨的地方,大家可以评论留言:
先定义一个消息注册派发器类:
MessageDispatch.h
#ifndef _MESSAGE_DISPATCH_
#define _MESSAGE_DISPATCH_#include <functional>
#include <unordered_map>
using namespace std::placeholders;typedef std::function<void(void*)> MsgCallBackFunc;class MessageDispatch {
public:MessageDispatch();~MessageDispatch();//注册协议ID msg_id对应成员回调函数pfuncvoid RegistMsgFunc(int msg_id, MsgCallBackFunc pfunc);//根据协议ID msg_id派发协议数据msgvoid DispatchMsgFunc(int msg_id, void* msg);private://协议注册列表std::unordered_map<int, MsgCallBackFunc> m_MsgFuncMap;
};extern MessageDispatch * g_MessageDispatch;#endif
MessageDispatch.cpp
#include "MessageDispatch.h"
MessageDispatch* g_MessageDispatch = new MessageDispatch;MessageDispatch::MessageDispatch() {m_MsgFuncMap.clear();
}MessageDispatch::~MessageDispatch() {
}void MessageDispatch::RegistMsgFunc(int msg_id, MsgCallBackFunc pfunc) {m_MsgFuncMap[msg_id] = pfunc;
}void MessageDispatch::DispatchMsgFunc(int msg_id, void* msg) {MsgCallBackFunc pfunc = m_MsgFuncMap[msg_id];if (pfunc) {pfunc(msg);}
}
定义一玩家类:
Actor.h
#ifndef _ACTOR_H_
#define _ACTOR_H_#include <iostream>
#include <vector>#include "MessageDispatch.h"
class SystemBase;//协议ID定义
enum MsgID
{cs_activate_a,cs_level_up_a,cs_activate_b,cs_level_up_b,
};//系统ID定义
enum SystemID {A, //A系统B, //B系统
};class Actor {
public:Actor();~Actor();void Init();void on_login();//消息注册 msg_id:协议id pfunc:成员回调函数void RegistMsg(int msg_id, MsgCallBackFunc pfunc);//消息派发 msg_id:协议id msg:协议数据void DispatchMsg(int msg_id, void* msg);private://玩家系统管理器std::unordered_map<int, SystemBase*> m_SystemMap;//玩家消息注册派发器MessageDispatch * m_MessageDispath;
};#endif
Actor.cpp
#include "Actor.h"
#include "System.h"
Actor::Actor() {
}Actor::~Actor() {for (auto iter = m_SystemMap.begin(); iter != m_SystemMap.end(); ++iter) {delete iter->second;}m_SystemMap.clear();delete m_MessageDispath;m_MessageDispath = nullptr;
}void Actor::Init() {//创建玩家自己的消息注册派发器m_MessageDispath = new MessageDispatch();//创建玩家的各个系统m_SystemMap[SystemID::A] = new ASystem(this);m_SystemMap[SystemID::B] = new BSystem(this);//初始化玩家各个系统for (auto iter = m_SystemMap.begin(); iter != m_SystemMap.end(); ++iter) {iter->second->OnInit();}
}void Actor::on_login() {//玩家各个系统登录事件调用for (auto iter = m_SystemMap.begin(); iter != m_SystemMap.end(); ++iter) {iter->second->OnLogin();}
}void Actor::RegistMsg(int msg_id, MsgCallBackFunc pfunc) {m_MessageDispath->RegistMsgFunc(msg_id, pfunc);
}void Actor::DispatchMsg(int msg_id, void* msg) {m_MessageDispath->DispatchMsgFunc(msg_id, msg);
}
定义系统类(这里为了更清晰演示,就直接在.h文件定义了):
#ifndef _SYSTEM_BASE_H_
#define _SYSTEM_BASE_H_
#include "Actor.h"#define REGIST_MSG(msg_id,func) m_Actor->RegistMsg(msg_id, std::bind(&func, this, _1));//系统基类,游戏所有的玩家系统都需要继承自该基类
class SystemBase {
public:SystemBase(Actor* actor) {m_Actor = actor;}virtual ~SystemBase() {std::cout << "SystemBase" << std::endl;};//初始化接口virtual void OnInit() = 0;//玩家登录接口virtual void OnLogin() = 0;//玩家下线存储数据接口virtual void Save() = 0;protected:Actor* m_Actor;
};class ASystem:public SystemBase {
public:ASystem(Actor* actor) :SystemBase(actor) {}~ASystem() {//玩家离线的时候存储一次数据Save();}virtual void OnInit() {std::cout << "ASystem Init" << std::endl;//注册协议id对应逻辑的成员函数REGIST_MSG(MsgID::cs_activate_a, ASystem::ClientActivate);REGIST_MSG(MsgID::cs_level_up_a, ASystem::ClientLevelUp);//为了更简便,将注册定义成REGIST_MSG宏//m_Actor->RegistMsg(MsgID::cs_activate_a, std::bind(&ASystem::ClientActivate, this, _1));//m_Actor->RegistMsg(MsgID::cs_level_up_a, std::bind(&ASystem::ClientLevelUp, this, _1));}virtual void OnLogin() {std::cout << "ASystem OnLogin" << std::endl;}virtual void Save() {std::cout << "ASystem Save" << std::endl;}//A系统激活操作void ClientActivate(void* msg) {std::cout << "ASystem ClientActivate msg:" << (char*)msg << std::endl;}//A系统升级操作void ClientLevelUp(void* msg) {std::cout << "ASystem ClientLevelUp msg:" << (char*)msg << std::endl;}
};class BSystem:public SystemBase {
public:BSystem(Actor* actor) : SystemBase(actor) {}~BSystem() {//玩家离线的时候存储一次数据Save();}virtual void OnInit() {std::cout << "BSystem Init" << std::endl;//注册协议id对应逻辑的成员函数REGIST_MSG(MsgID::cs_activate_b, BSystem::ClientActivate);REGIST_MSG(MsgID::cs_level_up_b, BSystem::ClientLevelUp);//m_Actor->RegistMsg(MsgID::cs_activate_b, std::bind(&BSystem::ClientActivate, this, _1));//m_Actor->RegistMsg(MsgID::cs_level_up_b, std::bind(&BSystem::ClientLevelUp, this, _1));}virtual void OnLogin() {std::cout << "BSystem OnLogin" << std::endl;}virtual void Save() {std::cout << "BSystem Save" << std::endl;}//B系统激活操作void ClientActivate(void* msg) {std::cout << "BSystem ClientActivate msg:" << (char*)msg << std::endl;}//B系统升级操作void ClientLevelUp(void* msg) {std::cout << "BSystem ClientLevelUp msg:" << (char*)msg << std::endl;}
};#endif
最后main函数测试调用:
#include <iostream>
#include "Actor.h"int main()
{Actor* actor = new Actor();actor->Init();actor->on_login();actor->DispatchMsg(MsgID::cs_activate_a, (void*)"cs_activate_a");actor->DispatchMsg(MsgID::cs_level_up_a, (void*)"cs_level_up_a");actor->DispatchMsg(MsgID::cs_activate_b, (void*)"cs_activate_b");actor->DispatchMsg(MsgID::cs_level_up_b, (void*)"cs_level_up_b");delete actor;return 0;
}
运行结果:
C++游戏服务器开发交流群:136961182,欢迎各位朋友一起交流
C++类成员函数作回调函数相关推荐
- c++类的成员函数作回调函数为啥要声明为static的
简单说明 C++的类成员函数不能像普通函数那样用于回调,因为每个成员函数都需要有一个对象实例去调用它. 把成员函数作为回调函数,可以把该成员函数声明为静态成员函数,但这样做有一个缺点,就是会破坏类的结 ...
- 将类的成员函数作为回调函数(外一篇:友元函数)
转自:http://blog.csdn.net/xylary/article/details/1548596 将类成员函数用做C回调函数 提出问题: 回调函数是基于C编程的Windows SDK的技 ...
- 如何定义和实现一个类的成员函数为回调函数
如果试图直接使用C++的成员函数作为回调函数将发生错误,甚至编译就不能通过.通过查询资料发现,其错误是普通的C++成员函数都隐含了一个传递函数作为参数,亦即"this"指针,C++ ...
- C++中 线程函数为静态函数 及 类成员函数作为回调函数(转载)
C++中 线程函数为静态函数 及 类成员函数作为回调函数 线程函数为静态函数: 线程控制函数和是不是静态函数没关系,静态函数是在构造中分配的地址空间,只有在析构时才释放也就是全局的东西,不管线程是否运 ...
- JavaScript高级day01-PM【对象、函数、回调函数、IIFE、this、关于语句分号问题、webstorm代码模板、复习】
笔记.视频.源码:JavaScript(基础.高级)笔记汇总表[尚硅谷JavaScript全套教程完整版] 目 录 P7 07.尚硅谷_JS高级_对象 23:30 1. 什么是对象? 2. 为什么 ...
- java 自定义函数的调用_Java/Android中的函数调用回调函数自定义回调函数
在做Android自定义控件时遇到要自定义回调函数的问题,想想自己还暂时没有那么精深的技术,赶紧返过头回来再重新研究Java中回调函数的问题.然而不幸的是,网上太多杂乱的帖子和博客都是转来转去,而且都 ...
- php变量函数,回调函数
一,变量可以直接传递函数 <?php function demo($num , $n )//$n是个函数 {for($i=0;$i<$num;++$i){if($n($i)){echo $ ...
- C++ 实现把非静态成员函数作为回调函数(非static)
众所周知,C++的类成员函数不能像普通函数那样用于回调,因为每个成员函数都需要有一个对象实例去调用它. 通常情况下,要实现成员函数作为回调函数,一种常用的方法就是把该成员函数设计为静态成员函数,但这样 ...
- JS闭包函数和回调函数
一.闭包 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现.闭包就是能够读取其他函数内部变量的函数.可以把闭包简单理解成"定义在一个函数 ...
最新文章
- 判断控件是否出现了滚动条
- python中循环结构关键字_Python的循环结构,也简单!
- 程序员大佬的简历和普通程序员有啥区别?
- php 抓取页面图片,php 抓取网页内容与图片的方法
- 漫画讲解HDFS原理
- java 多目录 编译jar_javac编译多个包下的、依赖其他jar包的java文件
- PHP常用函数(收集)
- Ubuntu16.04: 和 Windows-7 双系统启动顺序更改
- AS数据库自动备份的DOS语句
- apache的rewrite详解
- 计算机考研复试难,艰难与快乐:2008年重庆邮电大学计算机考研复试经历
- matlab 匹配滤波器,应用于雷达系统匹配滤波器的matlab仿真详解.doc
- junit5 入门系列教程-05-junit5 断言(assert)
- Unity查找图片被哪个Prefab引用
- keep跑步数据修改器_Keep蓄势变现
- USB对拷线Linux,USB对拷线设置教程
- wxpython使用_wxpython的demo使用
- matlab的wthcoef函数,小波去噪及其MATLAB中的函数.pdf
- 基于FBX SDK的FBX模型解析与加载 -(二)
- Hexo+Github轻松搭建个人博客
热门文章
- CodeForces - 13A Numbers【水题】
- elixir 教程_认识Elixir,Laravel编译资产的方式
- Vue CLl单个文件组件+多个文件组件
- Bzoj1502【NOI2005】月下柠檬树
- hadoop的journalnode节点出现Can‘t scan a pre-transactional edit log错误解决办法
- 蓝桥杯 算法训练 Cowboys
- 同样是倒排索引,Elasticsearch为何如此优秀?
- 【Python小游戏】俄罗斯方块
- 根据三角形的三条边长(长、中、短三条边),来判断三角形类型
- 0和5 (51Nod)