X-Macro
为什么
生成重复代码。比如ORM、序列化,这两个是最常见的需要大量重复或者相似代码的场景。
如何做
原理
用c++的预处理器配合头文件,生成重复的代码
场景
- 有一个数据类型,
Person
,字段全是string
类型,目前有name
address
两个 字段 - 后续会扩展
Person
,给它增加新的字段,比如familyMember
,gender
等 - 需要将对象支持序列化为字符串
- 需要知道类定义中共有多少个字段
文件结构:增删字段时,只修改一处
utils.h
中定义序列化方法string toString(Person const&)
和获取字段数方法size_t getNumberOfFields(Person const&)
person.h
中定义Person类,和它的字段
具体做法
如何在 person.h
中定义数据结构
#pragma once // 1
#define DATA_STRUCT Person // 2
#define ALL_DATA_FIELDS \ // 3
DATA_FIELD(name) \ // 4
DATA_FIELD(address) // 5
#include "utils.h" // 6
-
- 其他代码需要用到Person类时,需要引用
person.h
,需要加 include guard DATA_STRUCT
是类型的名字,它定义为Person,生成的类就叫PersonALL_DATA_FIELDS
是所有字段的集合,实现方法时会用到- 定义字段
name
- 定义字段
address
- 引用宏,预处理器会将头文件中的宏替换为Person,和name,address,生成我们需 要的代码
- 其他代码需要用到Person类时,需要引用
utils.h
不是普通的头文件
普通头文件中需要有 include guard避免重复引入,但是utils.h就是要对每个数据类型引 入一次,因此它不能有 include guard
如何实现 toString
方法?
在 utils.h
中:
#undef DATA_FIELD
#define DATA_FIELD(val) s += #val ":" + data.val + ",";
inline std::string
toString(DATA_STRUCT const& data)
{
std::string s;
ALL_DATA_FIELDS
return s;
}
如何实现 getNumberOfFields
方法?
#undef DATA_FIELD
#define DATA_FIELD(val) +1
inline size_t
getNumberOfFields(DATA_STRUCT const& data)
{
constexpr auto num = 0 ALL_DATA_FIELDS;
return num;
}
如果需要有更多的操作,比如生成sql,怎么办?
在 utils.h
中定义更多的操作,重新定义 DATA_FIELD
为需要的操作
优缺点
优点
- 增删字段时只用修改一处,简化编码,减少错误
- 分离了数据和操作,
utils.h
中全是操作,没有数据,person.h
中全是数据,没有 操作 - 比模板更灵活,更容易写
缺点
- 对不了解这个方法的人,代码不容易理解,即使用ide看代码,跳转到utils.h中,也并 不直观
- 即使了解这个方法,操作很多,很复杂的时候,维护也困难,ide难以提供语法检查,写 错了不好查出来
- 类型不安全,因为是宏实现的,容易出现漏掉类型转化、多余拷贝等类型问题