C++编程实践——magic_enum的应用

一、枚举的反射

在前面分析反射时,入门便是以枚举来举例的。其中有自己手写的代码也提供了几个优秀的开源框架。如果大家对这个感兴趣,或者对反射本身有兴趣可以翻看前面的相关文章。一般来说,枚举的应用都比较简单,这就意味着对枚举本身的一些特殊需求并不多。但总有一些场景下,枚举的数量可能非常多,这就产生一个重要的问题,可能对外开放的接口或相关消息等需要确定枚举ID与名称的映射关系(提高可读性)。这其实就是反射的一个重要的应用场景。

二、magic_enum库

最简单实现映射的方法就是自己手动写一个映射表,然后根据ID进行获取即可。但这样做的缺点非常明显,如果枚举体经常需要扩展或者会出现一些枚举体因为某种原因修改值的现象,手写的映射表就需要不断的跟着修改和完善,一旦有跟进不及时的情况就会出现错误。这时可以考虑使用开源的反射库,如magic_enum库。
magic_enum库,是一个相对简单易用的开源库,可以让开发者非常容易的实现上述的枚举值与名称的映射关系表。而且其是基于反射实现的,所以就避免了枚举本身修改后,需要开发者二次干预的问题。它实现的核心其实就是基于_PRETTY_FUNCTION_( 或 _FUNCSIG_)这个宏来展开的,类似的这些宏,在前面的文章也有分析。
magic_enum库的在应用前需要确定一下当前的环境特别是编译器是否支持,可使用下面的代码进行检查:

//  宏检查
#if defined(MAGIC_ENUM_SUPPORTED)
// OK
#endif
 
// 使用constexpr常量检查
if constexpr (magic_enum::is_magic_enum_supported) {
    // OK
}

通过上面的代码可以在编译时方便的检测是否支持magic_enum库。当然,如果一定要在非支持的环境上应用,可以通过定义宏MAGIC_ENUM_NO_CHECK_SUPPORT,来消除这个编译错误。

三、magic_enum库的应用场景和限制

magic_enum库的应用场景主要就是对枚举相关的值、字符串能及枚举名等各种操作和映射操作的场景应用。其实对于大多数的枚举反射应用,它显得功能有些过于丰富。不过,此库也有一些受限的情况需要开发者自行操作处理:

  1. 枚举值有范围限制,默认是 [-128, 128]
    现象就是如果枚举体中有超出范围的值定义,则无法进行反射。解决方法有两种:
//1 直接修改全局的宏定义
#define MAGIC_ENUM_RANGE_MIN 0
#define MAGIC_ENUM_RANGE_MAX 256
//2 修改指定枚举的大小
enum class Demo { A = 100, B = 200, C = 300 };
 
template <>
struct magic_enum::customize::enum_range<number> {
    //max和min值在short有或无符号的范围内
    static constexpr int min = 10;
    static constexpr int max = 1000;
};
  1. 枚举别名问题
    这个问题的解决只能靠编译器的支持了,如果无法解决,开发者只能自己操作将需要的原始名称放到别名之前
enum Demo{
  A = 0,
  B = 1,
  C = A,//别名放到后面
};
  1. 枚举范围可以修改,但过大会引起编译变慢
    这个问题不是一个大问题,但是如果程序本身规模不大,则需要考虑这个问题的影响是否会造成不适
  2. 编译器的支持,在MSVC、Clang和GCC中都有一些细节上的问题
    比如出现MSVC的智能感知无效以及clang中对模板中的枚举异常的问题。特别是出现“constexpr evaluation hit maximum step limit”时,需要根据情况进行相关的编译选项处理
  3. 需要进行标志位的处理
    这对于某些应用非常关键,处理的方法如下:
enum class Demo { 
    M = 1 << 0, 
    D = 1 << 1, 
    R = 1 << 2, 
    G = 1 << 3 
};
 
template <>
struct magic_enum::customize::enum_range<Demo> {
    static constexpr bool is_flags = true;
};
  1. 不支持前向声明的枚举反射
    这个没有办法,只能自己老实的不用前向声明即可。即必须让magic_enum库看到完全的枚举体的定义

四、例程

这里举一个简单的例程,供大家借鉴。更多的可以参看开源代码中的example文件夹下的相关例程。

#include "magic_enum.hpp"
#include <string>
enum class FLY_STATUS : int {
  STOP = 100,
  SLIDE = 200,
  MOVE = 300,
  FLYING = 400,
  LAND = 500,
};

template <> struct magic_enum::customize::enum_range<FLY_STATUS> {
  static constexpr int min = 0;
  static constexpr int max = 600;
};
template <typename E = Demo> std::string getEnumValueToName(int value) {
  E enumValue = static_cast<E>(value);

  std::string enumName = static_cast<std::string>(magic_enum::enum_name(enumValue));
  if (!enumName.empty()) {
    return enumName;
  } else {
    return "Unknown Enum Value";
  }
}

int main()
{
   std::string name = getEnumValueToName(static_cast<int>(FLY_STATUS::LAND));
   return 0;
}

五、总结

正如很多小说中所说,主人公最重要的不是自己能力多高而是要善于借势。其实,开发者使用第三方库或框架就类似于这种情况。特别是随着开发者的水平越来越高,回头看时,发现好多公司其实都是在国外的开源框架或库的基础上不断的修改完善,然后走出自己的一条路的。这就是学以致用的一种表现。与大家共勉!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值