开发者生态
morning
枚举到字符串的成本:C++26 反射与旧方法
2026-05-13
1 阅读
sagacity
两个月前,我发表了“C++26 反射的隐藏编译时成本”,其中我测量了包括 和执行一些基本反射的每个翻译单元的实际成本。如果您还没有阅读过,请从这里开始——这篇文章直接建立在它的基础上。该文章使用了预发布的 GCC 16 快照。从那时起,GCC 16 已正式发布 1 并且现已广泛使用,这似乎是一个很好的借口,可以通过一个更实际的示例重新审视该主题:枚举到字符串的转换。枚举到字符串是反射的“hello world”——但它在实际项目中也非常有用,例如日志记录、序列化、调试等。如果您在真实的代码库中采用反射,这可能是您要编写的第一件事。那么:与其他替代方案相比,在编译时基于反射的枚举到字符串的实际成本是多少?三种方法 我对同一操作的三种实现进行了基准测试:给定一个枚举值,返回一个 std::string_view 及其枚举器名称。 1. 反射 (c++26) 没有宏,没有样板,适用于任何枚举: #include #include #include template < typename T > 需要 std:: is_enum_v < T > constexpr std:: string_view to_enum_string ( T val ) { template for ( constexpr auto e : std:: Define_static_array ( std:: meta::enumerators_of (^^ T ))) { if ( val == [: e :]) return std:: meta::identifier_of ( e );返回“<未知>”;该代码取自“反射到底是什么?”作者:穆拉特·赫佩伊勒。我认为这是一个非常惯用的例子。 2. enchantum (c++17) ZXShady 的 C++17 仅头文件库,通过 __PRETTY_FUNCTION__ 解析技巧实现枚举反射。调用站点没有宏,不需要反射标志:#include enum class E { V0 , V1 , V2 , V3 }; std:: string_view s = enchantum :: to_string ( E :: V0 ); 3. x-macro(预处理器) C 风格的解决方案。列出枚举器一次,单个宏将扩展为枚举类定义和使用开关的 to_string 函数: #include "xmacro_enum.hpp" #define E_LIST ( X ) \ X ( V0 ) X ( V1 ) X ( V2 ) X ( V3 ) DEFINE_ENUM ( E , E_LIST ) // 生成: // enum class E { V0, V1, V2,V3}; // constexpr std::string_view to_string(E e) { ... } DEFINE_ENUM 的实现是几行预处理器粘合: #include #define XMACRO_VALUE_ ( name ) name , #define XMACRO_CASE_ ( name ) \ case EnumType_ :: name : return # name ; #define DEFINE_ENUM ( x_enum_name , x_list ) \ enum class x_enum_name { x_list ( XMACRO_VALUE_ ) }; \ constexpr std:: string_view to_string ( x_enum_name e ) \ { \ using EnumType_ = x_enum_name ; \ switch ( e ) { x_list ( XMACRO_CASE_ ) } \ return "<未知>" ;它引入的唯一标头是 。我们还将测试这种方法的一个变体,它返回 const char*,而标准库包含为零。对于每种方法,我创建了几个翻译单元: 定义一个带有 N 个枚举器的枚举类 E(V0、V1、…、V(N-1));包含枚举到字符串标头;使用运行时值调用一次转换函数以强制实例化。我在 4 、 16 、 64 、 256 和 1024 之间改变 N ,以了解成本如何随枚举大小变化。例如,N=4 的反射变体如下所示: #include "enum_to_string.hpp" enum class E { V0 , V1 , V2 , V3 };易失性 int 运行时_idx = 0 ; int main () { const E val = static_cast < E >(runtime_idx ); const auto sv = to_enum_string ( val ); return static_cast < int >( sv .size ()); enchantum 和 X-macro 变体在结构上是相同的 - 只是标题和函数调用发生了变化。我还单独测量了仅包含每个标头的成本,以将标头税与反射工作本身分开。 ⚠️ 关于 enchantum 的默认范围的注意事项: enchantum 扫描可配置范围内的枚举值(默认 [-256, 256] )。对于 N=1024 ,我必须将 ENCHANTUM_MAX_RANGE 提高到 1024 ,这也会减慢同一 TU 中的所有其他枚举的速度。阅读 N=1024 行时请记住这一点。所有基准测试文件均可在 GitHub 上获取。基准测试设置与上一篇文章相同 - 第 13 代 i9-13900K 上的 Fedora 44 Docker 容器内的超精细。这次有两点不同: 编译器:gcc 16.1.1 20260501(Red Hat 16.1.1-1,发布版本)——正式发布的 GCC 16,而不是预发布快照。噪声控制:容器使用 --cpuset-cpus=0-7 运行,主机设置为性能 CPU 调节器,编译器进程使用taskset -c 0 固定到 P 核。 hyperfine 使用 --warmup 5 --min-runs 20 运行。通常的免责声明:测量并不严格,我的硬件很强大(YMMV),并且单个 TU 数字低于项目范围的成本。此外,这些测量特定于 GCC 16 的当前反射和模块实现;其他编译器可能会表现出非常不同的行为。基准测试结果 每个 TU 的总编译时间 N X-macro ( const char* ) X-mac