C++模板元编程在项目中的应用
【摘要】 关于模板元技术,后续我会继续分享用其解决类型不同导致的代码重复问题。
近期同事问我如何统一Get/Set接口,大概如下:
struct Class {
void SetTimeStamp(timeval t);
timeval GetTimeStamp();
void SetFlag(uint32_t);
uint32_t GetFlag();
...
};
这种代码有很多Get/Set全都平铺到一个class了,后续随着业务还会扩展,接口也不统一,如何统一接口呢?
然后他想了一个办法,利用C++多态特性,可以把所有数据类型统一基类,然后基类指针就能存到关联容器里面了,从而统一接口GetData/SetData,代码大概如下:
enum DataInfoType {
DATA_GRADE, // <int32_t>
DATA_FLAG, // <uint32_t>
};
struct Data {};
template <typename T>
struct DataImpl: Data { T val; };
template <typename T>
void SetData(DataInfoType type, T val) {
// map<DataInfoType, std::shared_ptr<Data>> keyMap
keyMap[type] = std::make_shared<DataImpl<T>>(val);
}
template <typename T>
T GetData(DataInfoType type) {
// 基类到子类转换,用static_cast不安全!项目禁用rtti特性,不能使用安全的dynamic_cast
return static_pointer_cast<DataImpl<T>>(keyMap[type])->val;
}
这样做虽然统一接口了,但是不完全,因为从用户角度来说,需要同时指定DataInfoType和对应的类型,倘若用户不小心写错类型了,也能顺利编译通过,难以排查,如下:
float x = 3.14;
SetData(DATA_GRADE, x);
char y = GetData(DATA_GRADE);
在SetData的地方,没法检查DataInfoType和对应的类型是否匹配,当用户无意传入float类型,也能顺利存储值,最致命的是取出来的地方,由于static_cast导致从基类到具体子类的不安全转换,最终行为也是未定义的。
没法保证用户存入的类型和DataInfoType和取出来的类型互相匹配,导致未定义行为。
这时候可以上模板元技术了,来做编译期检查,一旦出现这种场景,导致编译不通过。
首先需要定义DataInfoType对应的类型,通过Traits类来绑定枚举与类型,输入枚举,输出类型:
enum DataInfoType {
DATA_GRADE, // <int32_t>
DATA_FLAG, // <uint32_t>
};
template<typename T>
struct Return { using type = T; }
template<DataInfoType>
struct InfoTypeTraits;
template<DataInfoType enumt>
using InfoTypeTraitsT = typename InfoTypeTraits<enumt>::type;
template<>
struct InfoTypeTraits<DATA_GRADE> : Return<int32_t> { };
template<>
struct InfoTypeTraits<DATA_FLAG> : Return<uint32_t> { };
然后提供一个Checkers类来提供DataInfoType与对应类型的检查:
template<DataInfoType type, typename T>
struct Checkers: std::is_same<T, InfoTypeTraitsT<type>> { };
最后提供用于编译期检查的Get/Set方法:
template<DataInfoType type, typename T>
void SetData(T&& val) {
// 编译期检查
static_assert(Checkers<type, std::decay_t<T>>::value, "SetData type differ!");
keyMap[type] = std::make_shared<DataImpl<T>>(val);
}
template<DataInfoType type>
InfoTypeTraitsT<type> GetData() {
return static_pointer_cast<DataImpl<T>>(keyMap[type])->val;
}
这样Set接口用户只需要提供DataInfoType和对应类型的值,由编译期检查来避免这种低级错误导致需要在运行期抓日志定位,从源头避免问题;其次Get接口只需要提供DataInfoType,不需要显式指定对应的类型了:
// float fx = 23.0;
// SetData<DATA_GRADE>(fx);
// error: checker.cpp:28:5: error: static_assert failed due to requirement 'Checkers<DATA_GRADE, float>::value' "SetData type differ!"
// static_assert(Checkers<type, std::decay_t<T>>::value, "SetData type differ!");
// ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// checker.cpp:41:5: note: in instantiation of function template specialization 'SetData<DATA_GRADE, float &>' requested here
// SetData<DATA_GRADE>(fx);
int32_t x = 0;
SetData<DATA_GRADE>(x);
auto y = GetData<DATA_GRADE>();
而后续扩展枚举类型,只需要新增对应的Traits类定义即可:
struct Test {
int x, y;
};
template<>
struct InfoTypeTraits<DATA_TEST>
{ using type = Test; };
// char z = GetData<DATA_TEST>();
// checker.cpp:58:10: error: no viable conversion from 'InfoTypeTraitsT<(DataInfoType)2U>' (aka 'Test') to 'char'
// char z = GetData<DATA_TEST>();
auto z = GetData<DATA_TEST>();
关于模板元技术,后续我会继续分享用其解决类型不同导致的代码重复问题。
----------------------
作者:netcan
【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
作者其他文章
评论(0)