Contents

虽然C++极力提倡使用const代替部分原来C中通过宏定义常量的方法,使用template代替部分生成代码的用法。但是不管对于C还是C++,现阶段宏仍有其无可替代的应用场景。

可以利用宏来简化部分高度相似的代码的编写已是众所周知的事情,但有时候我们想更进一步的解放自己的劳动力——设想,当设计一个游戏角色的新属性时,我们通常要做的事情至少是定义属性enum、创建对应属性类、在角色身上添加新成员、添加属性名——属性对象映射…

手动编码实现时,这是一个很繁琐的事情,涉及多个文件修改,大量重复代码。

程序猿也要有春天!必须搞定它。

由于目前阶段还做不到程序自我生产,所以对于这个问题,我们的目标仅仅是希望可以用一条语句,只提供少数必要的信息就能搞定所有事情。

前作《C++ RPC 设计》 中其实存在类似的问题,对于一条网络消息,我们需要处理enum、注册handle、创建msgtype-id映射、添加处理函数甚至为了减少编译依赖,我们还得为每条消息加上前置声明…

我感觉这事儿只有Macro能帮我们

…我想了想,觉得解释起来也比较麻烦,代码也不难懂,各位亲还是先自行浏览下吧:

核心代码:

// GenCodeMacroDef.h

#pragma once

namespace PtlMapper
{
	template <uint32 msgid> struct GetTypeById { };
	template <typename MsgType> struct GetIdByType { };
}

#define CREATE_MAPPER(msgid, msgtype) \
	namespace PtlMapper { \
	template < > struct GetTypeById<msgid> { typedef msgtype res; }; \
	template < > struct GetIdByType<msgtype> { enum { res = msgid }; }; \
}

// macro function def
#define GENCODE_NETWORK_ENUM(name) e##name,
#define GENCODE_NETWORK_MAPPER(name) CREATE_MAPPER(e##name, name)
#define GENCODE_NETWORK_FUNDEF(name) void OnRemoteMsg(const name& msg);
#define GENCODE_NETWORK_DECL(name) class name;
// macro function dispatcher
#define GENCODE_NETWORK(tag, name) GENCODE_NETWORK_##tag(name)

// macro function entrance for client
#define GENCODE_NETWORK_ALL_C2S(tag) \
	GENCODE_NETWORK(tag, C2S_Test) \
	GENCODE_NETWORK(tag, C2S_Hello)

// macro function entrance for server
#define GENCODE_NETWORK_ALL_S2C(tag) \
	GENCODE_NETWORK(tag, S2C_Test) \
	GENCODE_NETWORK(tag, S2C_Hello)

应用代码:

// PBPtlS2C.h

#pragma once
#include "GenCodeMacroDef.h"
#include "Server2Client.pb.h"

enum EPtlS2C
{
	eS2C_begin = 0x1234,
	GENCODE_NETWORK_ALL_S2C(ENUM)
	eS2C_end,
};

GENCODE_NETWORK_ALL_S2C(MAPPER);

--------------------------------------------
// CPipeEventClientHandler.h

#pragma once
#include "IPipeEventHandler.h"
#include "TPBMsgReceiver.h"
#include "TPBMsgSender.h"
#include "ISend.h"
#include "GenCodeMacroDef.h"

class CPipe;

GENCODE_NETWORK_ALL_S2C(DECL);

class CPipeEventClientHandler
	: public IPipeEventHandler
	, public TPBMsgSender<CPipeEventClientHandler>
	, public TPBMsgReceiver<CPipeEventClientHandler>
	, public ISend
{
public:
	CPipeEventClientHandler(CPipe* pipe);

	void Send(const char* data, size_t sz);

	virtual size_t OnRecv(const char* data, size_t sz);

	static void InitMsgHandle();

	// msg handler
	//void OnRemoteMsg(const S2C_Test& msg);
	//void OnRemoteMsg(const S2C_Hello& msg);
	GENCODE_NETWORK_ALL_S2C(FUNDEF);
	// ---
private:
	CPipe*			pipe_;
};

大体思路就是,在每个需要生成代码的地方设个“锚”——即使用特定的macro占位,然后在一个统一的地方实现这些macro,为了统一接口、使之看上去更像函数调用以及起到“一处定义处处生成的目的”,我们把不同的代码类型作为参数传给同一个macro,再由它分发给对应的子macro进行相应替换。

这样以后新加一个消息,就只需要在GenCodeMacroDef.h中加一行就搞定大部分苦力啦,OnRemoteMsg的实现用这个意义不大,还是得手写。另外可以看出,扩展起来也是很方便的。

唯一我觉得美中不足的地方就是…有的工友会不喜欢这种设计,以至于看这种代码经常让他们感到烦躁 ~_~

需要查看完整代码点这里:https://github.com/ooomceg/examples

Contents