一、Policy-Based 设计:用模板参数做配置策略
Policy-Based 设计,说白了就是「把行为当作参数传进去」。
我最早接触这个思想是在写一个内存池的时候。当时项目里需要支持多种分配策略——有的场景要快,有的场景要安全,有的场景要统计。如果每个场景都写一个类,代码量爆炸。后来我用了 Policy 设计,一个模板类搞定所有场景。
嗯,今天我们就来聊聊这个。
1.1 什么是 Policy-Based 设计?
Policy-Based 设计的核心思想是:把类的行为拆分成多个策略(Policy),每个策略用一个模板参数表示。使用者通过组合不同的策略,得到不同的类。
举个例子:
template <typename ThreadPolicy, typename LockPolicy>
class DataBuffer {
// 使用 ThreadPolicy 决定是否线程安全
// 使用 LockPolicy 决定锁的类型
};
你看,这个 DataBuffer 本身不关心线程安全怎么实现。它只负责调用 ThreadPolicy 提供的接口。具体是单线程、多线程、读写锁,全由模板参数决定。
核心思想: 将「做什么」和「怎么做」分离。类只定义「做什么」,Policy 定义「怎么做」。
1.2 为什么需要 Policy-Based 设计?
我遇到过不少团队,代码里充斥着这样的写法:
// 方案A:用宏控制
#ifdef USE_THREAD_SAFE
class DataBuffer { /* 加锁版本 */ };
#else
class DataBuffer { /* 不加锁版本 */ };
#endif
// 方案B:用继承
class ThreadSafeBuffer : public DataBuffer { /* 重写加锁方法 */ };
class SingleThreadBuffer : public DataBuffer { /* 空实现 */ };
这两种方案都有问题。宏方案让代码难以阅读和维护,继承方案则会产生大量子类。你想想看,如果策略有 3 个维度,每个维度有 2 种选择,继承方案就要写 8 个类。
Policy-Based 设计用模板组合,只需要 1 个模板类 + 6 个 Policy 实现,使用者自由组合:
using SafeBuffer = DataBuffer<MultiThread, SpinLock>;
using FastBuffer = DataBuffer<SingleThread, NoLock>;
干净、清晰、可扩展。
1.3 Policy 的基本结构
一个 Policy 通常是一个类或类模板,它定义了一组接口约定。Policy 本身不单独使用,而是作为模板参数注入到宿主类中。
来看一个完整的例子:
// 定义 Policy:分配策略
struct MallocAlloc {
static void* allocate(size_t size) {
return malloc(size);
}
static void deallocate(void* p) {
free(p);
}
};
struct PoolAlloc {
static void* allocate(size_t size) {
return pool_alloc(size); // 假设有池分配器
}
static void deallocate(void* p) {
pool_free(p);
}
};
// 宿主类
template <typename AllocPolicy>
class Container {
public:
void* getBuffer(size_t size) {
return AllocPolicy::allocate(size);
}
void releaseBuffer(void* p) {
AllocPolicy::deallocate(p);
}
};
// 使用
using FastContainer = Container<PoolAlloc>;
using SimpleContainer = Container<MallocAlloc>;
小技巧: Policy 通常使用静态方法,这样不需要创建 Policy 对象。如果 Policy 需要状态,也可以使用非静态方法,但要注意生命周期管理。
1.4 Policy 的接口约定
Policy 的接口约定是隐式的——通过模板实例化时的编译检查来保证。如果某个 Policy 没有提供宿主类要求的接口,编译就会报错。
我个人习惯在文档中明确写出 Policy 需要满足的接口:
| 接口 | 说明 | 返回值 |
|---|---|---|
allocate(size) |
分配指定大小的内存 | void* |
deallocate(ptr) |
释放内存 | void |
这样,使用者一看就知道该怎么写自己的 Policy。
1.5 多个 Policy 的组合
实际项目中,一个类往往需要多个 Policy。比如一个线程安全的日志系统,可能需要:
- 输出策略: 写文件、写网络、写控制台
- 格式化策略: 纯文本、JSON、XML
- 过滤策略: 按级别过滤、按模块过滤
用 Policy-Based 设计,可以这样组合:
template <typename OutputPolicy, typename FormatPolicy, typename FilterPolicy>
class Logger {
public:
void log(const std::string& msg, int level) {
if (!FilterPolicy::shouldLog(level)) return;
auto formatted = FormatPolicy::format(msg, level);
OutputPolicy::write(formatted);
}
};
// 定义具体策略
struct FileOutput { static void write(const std::string& s) { /* 写文件 */ } };
struct JsonFormat { static std::string format(const std::string& msg, int level) { /* 转 JSON */ } };
struct LevelFilter { static bool shouldLog(int level) { return level >= WARN; } };
// 组合使用
using FileJsonLogger = Logger<FileOutput, JsonFormat, LevelFilter>;
注意: Policy 的数量不宜过多。我见过有人把 7、8 个 Policy 塞进一个模板类,结果代码可读性极差。一般来说,3-5 个 Policy 是比较合理的范围。
1.6 Policy 与默认参数
为了让使用者更方便,可以给 Policy 提供默认值:
template <
typename OutputPolicy = FileOutput,
typename FormatPolicy = PlainTextFormat,
typename FilterPolicy = AllPassFilter
>
class Logger { /* ... */ };
// 使用者可以只指定需要的 Policy
using SimpleLogger = Logger<>; // 全部默认
using JsonLogger = Logger<FileOutput, JsonFormat>; // 只改格式化策略
这样既保留了灵活性,又降低了使用门槛。
1.7 我踩过的坑
我曾经在一个项目中,把 Policy 的接口设计得过于灵活——允许 Policy 返回不同的类型。结果导致模板实例化时出现各种奇怪的编译错误,调试了整整两天。
后来我总结了一条原则:Policy 的接口签名应该尽量统一。如果确实需要返回不同类型,可以用 using 类型别名来约定:
struct PolicyA {
using result_type = int;
static result_type compute() { return 42; }
};
struct PolicyB {
using result_type = double;
static result_type compute() { return 3.14; }
};
这样宿主类可以通过 typename Policy::result_type 来获取返回类型,既灵活又可控。
1.8 知识体系总览
下面这张图展示了 Policy-Based 设计的核心结构:
从图中可以看到,宿主类通过模板参数接收 Policy,Policy 再绑定到具体的实现。使用者只需要组合不同的 Policy,就能得到不同的行为。
1.9 什么时候用 Policy-Based?
不是所有场景都适合用 Policy-Based 设计。我个人的经验是:
- 适合: 类的行为有多个独立维度,每个维度有 2 种以上选择
- 适合: 需要在编译期确定策略,运行时不需要切换
- 不适合: 策略需要在运行时动态切换(用策略模式更合适)
- 不适合: 策略数量极少,且不会扩展(用简单继承就够了)
一句话总结: Policy-Based 设计是编译期的策略模式。它用模板参数代替虚函数,用编译期绑定代替运行时多态。代价是代码体积可能增大,但换来的是零运行时开销和极致的灵活性。
好了,这一章就到这里。Policy-Based 设计是 C++ 模板元编程中非常实用的技术,希望你能在实际项目中用起来。
公众号:蓝海资料掘金营,微信deep3321