为什么
在一些编码实现中通常都有数据库或者属性接口,用来根据Key存取各种类型的数据,譬如:
class OldImpl
{
public:
bool has(const std::string& key) const;
void setAsInt(const std::string& key, int v);
void setAsDouble(const std::string& key, double v);
void setAsString(const std::string& key, const std::string& v);
int asInt(const std::string& key);
double asDouble(const std::string& key);
std::string asString(const std::string& key);
};
实际上只有3种不同功能的接口:是否存在、读取、写入,能否将这些接口转换成对应的模板接口?
目标
在原有接口基础上提供模板接口,如下:
class OldImpl
{
public:
template<typename T>
bool has(const std::string& key) const;
template<typename T>
void set(const std::string& key, const T& v);
template<typename T>
T as(const std::string& key);
};
使得可以以如下方法使用:
void test() {
OldImpl impl;
if (!impl.has<int>("a")) {
impl.set<int>("a", 'a');
}
auto x = impl.as<double>("x");
auto y = impl.as<double>("y");
auto z = x + y;
impl.set("z", z);
}
如何实现写入的模板接口
首先,原有的接口是可以写成如下形式的:
void set(const std::string& key, int v);
void set(const std::string& key, double v);
void set(const std::string& key, const std::string& v);
这样的话写入的模板接口可以以如下方式实现:
template<typename T>
void set(const std::string& key, const T& v) {
set(key, v);
}
编译时根据v
的类型即可找到正确的写入接口,如果调用写入接口时指定T
,可以利用C++的自动类型转换/构造来写入指定类型的值。
如何实现读取的模板接口
在实现写入接口时,可以根据函数参数类型的不同区分不同的接口,但是读取的模板接口仅有一个相同类型的参数,根据函数返回值是无法区分的,这时候就需要用到Tag Dispatch;
C++是强类型语言,在名称相同的情况下,可以根据参数类型的不同来区分不同的接口;可以使用类模板定义不同的类型:
template<typename T>
struct TypeTag {};
采用上述的方式声明类模板,那么TypeTag<int>
及TypeTag<double>
等一系列都是不同的类型:
这时可以将读取接口写成如下方式:
int as(const std::string& key, TypeTag<int>);
double as(const std::string& key, TypeTag<double>);
std::string as(const std::string& key, TypeTag<std::string>);
而读取接口模板实现如下:
template<typename T>
T as(const std::string& key) {
return as(key, TypeTag<T>());
}
如何实现是否存在接口
通常情况下原本的has
接口是直接可用的,如果需要判定某个Key及指定数据类型的值是否存在,可以参照读取接口的实现。
学到的内容
- Tag Dispatch