对于实现基础框架的人来说,C++ 的可变模版参数真是太有用了,能够瞬间解决很多棘手的问题。下面这个例子就能够充分说明其各种强大的使用方式。
先说一下这个例子的背景。
我们有一些配置参数,组合在一起形成若干的配置类。
为了统一处理,定义的配置项目抽象,每个配置项目与一个配置参数对应。上层只能通过配置项目实例来读、写对应参数的值。
一个类里面的参数存在一些关联性,在改变某个参数值的时候,有可能与其他参数冲突,所以需要校验,执行校验的方法称为一致性校验器。
我们为配置类添加多个(种)一致性校验器,每个校验器面向的一组有关联性的参数。
template
class ConfigItem : ConfigItemBase
{
};
class ConfigBase
{
};
class ConfigClass
{
public:
ConfigClass();
virtual void * addrOf(ConfigBase & cfg, ConfigItemBase const &) const;
template
void addValidator(F validator, ConfigItem const & ... items);
typedef std::function validator_t;
private:
struct Validator
{
std::vector items;
validator_t validator;
};
std::vector validators_;
};
如上代码:
template
inline void ConfigClass::addValidator(F validator, ConfigItem const & ...items)
{
validators_.push_back({&items...}, [this, validator, &items...](ConfigBase & cfg) {
validator(*reinterpret_cast(addrOf(cfg, items))...);
});
}
看看这段代码哪些地方用了可变模版参数,对了,就是所有三个点(...)的地方,我们来分析每一处对可变模版参数的使用方式。
这是最开始声明可变模版参数的地方,也就是我们的模版方法可以有不定数量的模版参数。
这里有两点。
首先将可变模版参数 I 作为另一个模版类 ConfigItem
其次是作为方法的输入参数的类型,这里方法输入了不定数量的参数 items,并且参数的类型是对可变模版参数做了一定变化的 —— 除了变成另一个 ConfigItem
这里其实是要生成一个 std::vector
另外这里的取地址运算符 &,作用于所有不定数量的变量。
这里把可变模版参数修饰的变量,作为 lambda 表达式的环境参数传给 lambda 方法。
另外这里的取地址运算符 &,实际上表示使用引用的方式将变量传给 lambda 方法,并不是取地址。
可以看到,这里可变模版参数被用来作为类型转换运算符的类型参数。
这里把可变模版参数修饰的变量,分别作为另一个普通方法的参数,然后将返回值组合为另外一组不定数量的值,作为参数传给另一个方法(先经过若干运算符转换)。该方法的参数数目与可变模版参数的数量一样,类型有相关性。
最后我们来看看如何使用 addValidator 方法给参数元类添加一致性校验器。
static ConfigItem item_bool;
static ConfigItem item_int;
ConfigClass::ConfigClass()
{
addValidator([](bool & b, int & i) {
// DO validate
}, item_bool, item_int);
}
上面的 lambda 表达式就是一个一致性校验器,它处理了两个参数的关联性,一个 bool 类型(item_bool)参数,一个 int 类型 (item_int)参数。
在一致性校验器内部,框架已经提供了这两个参数的当前值,校验器可以判断这两个值是否冲突,然后直接修改参数的值,是不是方便得不能再方便了?
可这么强大的机制,用可变模版参数实现起来就是小菜一碟,回过头再看看 addValidator 的实现吧,只有简简单单的 7 行!
还有好多 C++ 形式可以用可变模版参数扩展,你也可以继续试一试。