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对应成员回调函数pfunc
	void RegistMsgFunc(int msg_id, MsgCallBackFunc pfunc);
    //根据协议ID msg_id派发协议数据msg
	void 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,欢迎各位朋友一起交流

  • 10
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

春休夏末

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值