前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++ rtti vs 宏 - 如何优雅的获取类型T的name或ID

C++ rtti vs 宏 - 如何优雅的获取类型T的name或ID

作者头像
fangfang
发布2021-10-29 15:31:18
2.3K0
发布2021-10-29 15:31:18
举报
文章被收录于专栏:方方的杂货铺

1. RTTI的实现

如何获取一个类型T的name或者唯一ID, 对于这个问题, 最常规的方式应该是借助C++的rtti了, 比如如ponder中所使用的方式:

代码语言:javascript
复制
namespace ponder {
namespace detail {

typedef std::type_index TypeId; // Used to uniquely identify a type.

// Calculate TypeId for T
template <typename T>
inline TypeId calcTypeId() {return TypeId(typeid(T)); }
}
}

其中的TypeID其实就是std::type_index, ponder中大量依赖这种方式进行 T -> std::type_index的转换, 作为常规实现, 它具有以下特点:

  1. 它确实能够比较好的生成一个自带hash实现的std::type_index, 可以很好的用它作为key来索引类型相关的对象.
  2. 利用相同的方法也可以很容易的获取类型T的字符串名称.
  3. 在rtti开启的情况下, 功能直接可获取.

但不难发现, 这种方法也存在以下问题:

  1. 首先是依赖rtti, 在一些rtti关闭的情况下, 对应机制就停摆了
  2. 很重要的一点, 这是一个runtime实现, 也就是说, 天生与constexpr无缘, 我们没有办法做任何善于compiler time的优化.
  3. std::type_index的实现一言难尽, 虽然你可以通过hash获取一个size_t类型的ID, 但对于这个值的稳定性和有效性都依赖于库底层的实现, 当一些特定的场合需要跨App进行ID标识的时候, 它基本就没啥用了, 你只能退回字符串名称的方式去处理相关逻辑了.

2. FUNCSIG系统宏的实现

那么有没有一种实现是不依赖rtti, 又能很好的适配constexpr的呢? 答案是肯定的. 比如decs中对类型T的hash()方式:

代码语言:javascript
复制
struct MetatypeHash {
        size_t name_hash{ 0 };
        size_t matcher_hash{ 0 };

        bool operator==(const MetatypeHash& other)const {
            return name_hash == other.name_hash;
        }
        template<typename T>
        static constexpr const char* name_detail() {
            return __FUNCSIG__;
        }

        template<typename T>
        static constexpr size_t hash() {

            static_assert(!std::is_reference_v<T>, "dont send references to hash");
            static_assert(!std::is_const_v<T>, "dont send const to hash");
            return hash_fnv1a(name_detail<T>());
        }
    };

如代码所示, 很好的解决了compiler time生成name_detail()和hash()的问题, hash这个地方使用了一个hash_fnv1a()的算法, 不是本文关注的重点, 这里不展开了. 通过constexpr, 很好的解决了编译期获取类型T的唯一字符名称和64位hash的问题, 而且不依赖rtti, 比较优雅的解决了T->name和T->id的问题.

当然, 上面的代码其实没有解决跨平台的问题, 另外对某一类型T, 获取到的其实是类似 "const char *__cdecl ecs::MetatypeHash::name_detail(void)"这样的值, 如果我们需要关注name, 以及需要跨平台, 那么显然以上的设施还不够, 但最核心的问题解决了, 其它的就比较简单了, 因为代码比较简单, 这里直接贴代码了(感谢黄老板@spiritsaway 的指导), 最后实现的代码如下:

代码语言:javascript
复制
struct MetatypeHash {
        size_t name_hash{ 0 };
        size_t matcher_hash{ 0 };

        bool operator==(const MetatypeHash& other)const {
            return name_hash == other.name_hash;
        }
        template<typename T>
        static constexpr const char* name_detail() {
#if RSTUDIO_CORE_PLATFORM == RSTUDIO_CORE_PLATFORM_LINUX
            return __PRETTY_FUNCTION__;
#else
            return __FUNCSIG__;
#endif
        }

#if RSTUDIO_CORE_PLATFORM != RSTUDIO_CORE_PLATFORM_WIN32
        template<typename T>
        static constexpr auto name_pretty()
        {
            //name_detail() is like: static constexpr const char* ecs::MetatypeHash::name_detail() [with T = rstudio::math::Vector3]
            std::string_view pretty_name = name_detail<T>();
            std::string_view prefix = "static constexpr const char* ecs::MetatypeHash::name_detail() [with T = ";
            std::string_view suffix = "]";
            pretty_name.remove_prefix(prefix.size());
            pretty_name.remove_suffix(suffix.size());
            return pretty_name;
        }
#else
        template<typename T>
        static constexpr auto name_pretty()
        {
            //name_detail() is like "const char *__cdecl ecs::MetatypeHash::name_detail<class rstudio::math::Vector3>(void)"
            std::string_view pretty_name = name_detail<T>();
            std::string_view prefix = "const char *__cdecl ecs::MetatypeHash::name_detail<";
            std::string_view suffix = ">(void)";

            pretty_name.remove_prefix(prefix.size());
            pretty_name.remove_suffix(suffix.size());

            size_t start_of = pretty_name.find_first_of(' ') + 1;
            return pretty_name.substr(start_of);
        }
#endif

        template<typename T>
        static constexpr size_t hash() {

            static_assert(!std::is_reference_v<T>, "dont send references to hash");
            static_assert(!std::is_const_v<T>, "dont send const to hash");
            return hash_fnv1a(name_detail<T>());
        }
    };

这样一些不需要友好名字的地方我们可以使用name_detail(), 一些需要友好名字的地方, 我们使用name_pretty(), 至于hash(), 看自己的使用场合, 可以灵活选用name_detail()或者name_pretty()了, 以上实现在VS2019和GCC8.3上测试过, clang的话需要处理一下, 宏跟Linux下一样都是 PRETTY_FUNCTION, 但前缀会稍有区别, 对应的name_pretty()实现需要做下调整, 手边没有安装Clang的机器, 就先不贴出适配Clang的版本了.

这种compiler time的优化对于支持C++17特性的编译器来说, 还是值得去做的, 一些高频操作, 参与Deduce的类型又比较可预估的时候, 我们肯定会倾向于用更多的compiler time实现来取代像前文提到的rtti这种runtime 实现了.

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. RTTI的实现
  • 2. FUNCSIG系统宏的实现
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档