为什么

生成重复代码。比如ORM、序列化,这两个是最常见的需要大量重复或者相似代码的场景。

如何做

原理

用c++的预处理器配合头文件,生成重复的代码

场景

  1. 有一个数据类型, Person ,字段全是 string 类型,目前有 name address 两个 字段
  2. 后续会扩展 Person ,给它增加新的字段,比如 familyMembergender
  3. 需要将对象支持序列化为字符串
  4. 需要知道类定义中共有多少个字段

文件结构:增删字段时,只修改一处

  1. utils.h 中定义序列化方法 string toString(Person const&) 和获取字段数方法 size_t getNumberOfFields(Person const&)
  2. 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
    1. 其他代码需要用到Person类时,需要引用person.h ,需要加 include guard
    2. DATA_STRUCT 是类型的名字,它定义为Person,生成的类就叫Person
    3. ALL_DATA_FIELDS 是所有字段的集合,实现方法时会用到
    4. 定义字段 name
    5. 定义字段 address
    6. 引用宏,预处理器会将头文件中的宏替换为Person,和name,address,生成我们需 要的代码

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 为需要的操作

优缺点

优点

  1. 增删字段时只用修改一处,简化编码,减少错误
  2. 分离了数据和操作,utils.h 中全是操作,没有数据, person.h 中全是数据,没有 操作
  3. 比模板更灵活,更容易写

缺点

  1. 对不了解这个方法的人,代码不容易理解,即使用ide看代码,跳转到utils.h中,也并 不直观
  2. 即使了解这个方法,操作很多,很复杂的时候,维护也困难,ide难以提供语法检查,写 错了不好查出来
  3. 类型不安全,因为是宏实现的,容易出现漏掉类型转化、多余拷贝等类型问题