C++模板元编程在项目中的应用

举报
技术火炬手 发表于 2020/07/17 14:42:33 2020/07/17
【摘要】 关于模板元技术,后续我会继续分享用其解决类型不同导致的代码重复问题。

近期同事问我如何统一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

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。