C++怎么解决不支持字符串枚举?

时间:2024-07-06 18:27:29

首先,有两种方法:使用命名空间和字符串常量与使用 enum class 和辅助函数。

表格直观展示

特性 使用命名空间和字符串常量 使用 enum class 和辅助函数
类型安全性 低 - 编译器无法检查字符串有效性,运行时发现错误 高 - 编译期类型检查,非法类型在编译期即可发现
性能 低 - 字符串比较时间复杂度为 O(n),逐字符比较有开销 高 - 枚举值比较时间复杂度为 O(1),查找效率高,平均查找为 O(1)
可维护性 中 - 易于理解和使用,但字符串容易拼写错误 高 - 易读易维护,枚举类型和映射关系可以方便管理和扩展
代码简洁性 中 - 可能需要反复访问命名空间中的字符串常量 高 - 代码简洁且易读,枚举结合辅助函数方便处理
安全性 中 - 字符串误输入导致运行时错误 高 - 通过类型检查避免运行时崩溃
易用性 中 - 需要记住和管理字符串常量,容易犯拼写错误 高 - 枚举值可以通过智能提示和自动补全,大幅减少错误

代码展示 

使用命名空间和字符串常量 

优点

  1. 直观和易于理解:字符串表示类型或状态非常直观,容易理解和使用。
  2. 与外部系统交互方便:许多外部系统或配置文件使用字符串表示状态或类型,直接使用字符串便于与这些系统交互。

缺点

  1. 类型不安全:任何字符串都可以传入,编译器无法检查其有效性,拼写错误会导致运行时错误。
  2. 性能开销较大:字符串比较和逐字符检查的时间复杂度为 O(n),性能较低,尤其是在处理长字符串时。
  3. 可维护性差:字符串常量需要在代码中多次引用,拼写错误不易发现,维护困难。
namespace ContainerType {
    const std::string SimpleView = "SimpleView";
    const std::string SimpleViewPage = "SimpleViewPage";
    const std::string ListView = "ListView";
    const std::string ListViewPage = "ListViewPage";
    const std::string Dialog = "Dialog";
}

void handleContainerType(const std::string& type) {
    if (type == ContainerType::SimpleView) {
        std::cout << "Handling SimpleView" << std::endl;
    } else if (type == ContainerType::SimpleViewPage) {
        std::cout << "Handling SimpleViewPage" << std::endl;
    } else if (type == ContainerType::ListView) {
        std::cout << "Handling ListView" << std::endl;
    } else if (type == ContainerType::ListViewPage) {
        std::cout << "Handling ListViewPage" << std::endl;
    } else if (type == ContainerType::Dialog) {
        std::cout << "Handling Dialog" << std::endl;
    } else {
        std::cout << "Unknown type" << std::endl;
    }
}

使用 enum class 和辅助函数

优点

  1. 类型安全:编译器进行类型检查,非法类型在编译期即可发现,避免运行时错误。
  2. 性能高效:枚举值比较时间复杂度为 O(1),使用哈希表进行字符串到枚举的映射查找平均为 O(1),高效。
  3. 代码可维护性高:枚举值清晰明了,降低拼写错误风险,方便代码维护。
  4. 易于扩展:可以方便地添加新的枚举值和映射关系。

缺点

  1. 稍微复杂:需要维护枚举和映射关系,初次使用时可能需要更多理解。
enum class ContainerType {
    SimpleView,
    SimpleViewPage,
    ListView,
    ListViewPage,
    Dialog,
    Unknown
};

// 辅助函数:字符串 -> ContainerType
ContainerType stringToContainerType(const std::string& typeStr) {
    static const std::unordered_map<std::string, ContainerType> stringToEnumMap = {
        {"SimpleView", ContainerType::SimpleView},
        {"SimpleViewPage", ContainerType::SimpleViewPage},
        {"ListView", ContainerType::ListView},
        {"ListViewPage", ContainerType::ListViewPage},
        {"Dialog", ContainerType::Dialog}
    };
    auto it = stringToEnumMap.find(typeStr);
    if (it != stringToEnumMap.end()) {
        return it->second;
    }
    return ContainerType::Unknown;
}

void handleContainerType(ContainerType type) {
    switch (type) {
        case ContainerType::SimpleView:
            std::cout << "Handling SimpleView" << std::endl;
            break;
        case ContainerType::SimpleViewPage:
            std::cout << "Handling SimpleViewPage" << std::endl;
            break;
        case ContainerType::ListView:
            std::cout << "Handling ListView" << std::endl;
            break;
        case ContainerType::ListViewPage:
            std::cout << "Handling ListViewPage" << std::endl;
            break;
        case ContainerType::Dialog:
            std::cout << "Handling Dialog" << std::endl;
            break;
        default:
            std::cout << "Unknown type" << std::endl;
            break;
    }
}

int main() {
    ContainerType type = ContainerType::SimpleView;
    handleContainerType(type);

    type = stringToContainerType("Dialog");
    handleContainerType(type);

    return 0;
}

开源项目magic_enum

magic_enum 是一个 C++17 的头文件库,提供了对枚举的静态反射功能。它允许你在不使用宏或样板代码的情况下,轻松地将枚举类型与字符串进行转换,并实现一系列有关枚举的高效操作。有如下类:

enum class Color { RED = 1, BLUE, GREEN, NONE };
功能 描述 示例代码
枚举值转字符串 将枚举值转换为字符串 auto color_name = magic_enum::enum_name(color);
字符串转枚举值 将字符串转换为枚举值(不区分大小写、二元谓词比较) auto color = magic_enum::enum_cast<Color>(color_name);
auto color = magic_enum::enum_cast<Color>("green", magic_enum::case_insensitive);
auto color = magic_enum::enum_cast<Color>("green", [](char lhs, char rhs) { return std::tolower(lhs) == std::tolower(rhs); });
整数转枚举值 将整数转换为枚举值 auto color = magic_enum::enum_cast<Color>(color_integer);
通过索引访问枚举值 根据索引获取枚举值 Color color = magic_enum::enum_value<Color>(i);
获取枚举值序列 获取所有枚举值的序列 constexpr auto colors = magic_enum::enum_values<Color>();
获取枚举元素数量 获取枚举的元素个数 constexpr std::size_t color_count = magic_enum::enum_count<Color>();
获取枚举值的整数 获取枚举值对应的整数 auto color_integer = magic_enum::enum_integer(color);
auto color_integer = magic_enum::enum_underlying(color);
获取枚举名称序列 获取所有枚举值的名称序列 constexpr auto color_names = magic_enum::enum_names<Color>();
获取枚举条目序列 获取所有枚举值和名称的序列 constexpr auto color_entries = magic_enum::enum_entries<Color>();
检查枚举是否包含某个值 检查枚举类型是否包含某个值 magic_enum::enum_contains(Color::GREEN);
magic_enum::enum_contains<Color>(2);
magic_enum::enum_contains<Color>("GREEN");
获取枚举值的索引 获取枚举值在序列中的索引 constexpr auto color_index = magic_enum::enum_index(Color::BLUE);
枚举的位运算支持 对枚举类型进行位运算 using namespace magic_enum::bitwise_operators;
`Flags flags = Flags::A
标志枚举支持 对标志枚举进行特殊处理 template <> struct magic_enum::customize::enum_range<Directions> { static constexpr bool is_flags = true; };
`auto name = magic_enum::enum_flags_name(Directions::Up
枚举类型的名称 获取枚举类型的名称 auto type_name = magic_enum::enum_type_name<decltype(color)>();
ENUM 类型换为在IO流的操作符 为枚举添加 ostream 和 istream 的支持 using magic_enum::iostream_operators::operator<<;
using magic_enum::iostream_operators::operator>>;
检查是否为无范围枚举 判断类型是否为无范围枚举 magic_enum::is_unscoped_enum<color>::value;
magic_enum::is_unscoped_enum_v<color>;
检查是否为范围枚举 判断类型是否为范围枚举 magic_enum::is_scoped_enum<direction>::value;
magic_enum::is_scoped_enum_v<direction>;
Bitwise 操作符 为枚举类型提供位操作符 using namespace magic_enum::bitwise_operators;
`Flags flags = Flags::A
静态存储枚举变量到字符串 更轻量的编译时间和不限于 enum_range 的限制 constexpr auto color_name = magic_enum::enum_name<color>();

一个简单的示例:

#include <iostream>
#include <string>
#include <magic_enum.hpp>

enum class Color { RED = 1, BLUE, GREEN, NONE };

int main() {
    // 枚举值转字符串
    Color color1 = Color::RED;
    std::cout << "Color 1: " << magic_enum::enum_name(color1) << std::endl;

    // 字符串转枚举值
    std::string color_name{"GREEN"};
    auto color2 = magic_enum::enum_cast<Color>(color_name);
    if (color2.has_value()) {
        std::cout << "Color 2: " << magic_enum::enum_name(color2.value()) << std::endl;
    }

    return 0;
}

遇到的问题

main.cpp:3:10: fatal error: 'magic_enum.hpp' file not found #include <magic_enum.hpp> ^~~~~~~~~~~~~~~~ 1 error generated.

解决方法:

  1. 下载并包含 magic_enum 库。你可以从 magic_enum 的 GitHub 页面上下载最新的版本:GitHub - Neargye/magic_enum: Static reflection for enums (to string, from string, iteration) for modern C++, work with any enum type without any macro or boilerplate code
  2. magic_enum.hpp 头文件放在你的项目的合适目录中,例如 include 目录。
  3. 编译 
g++ -std=c++17 -Iinclude main.cpp -o main

如果你使用 CMake 进行项目构建,可以通过 FetchContentmegic_enum 集成到你的项目中。

   cmake_minimum_required(VERSION 3.11)

   project(MyProject)

   include(FetchContent)
   FetchContent_Declare(
     magic_enum
     GIT_REPOSITORY https://github.com/Neargye/magic_enum.git
     GIT_TAG v0.7.3
   )
   FetchContent_MakeAvailable(magic_enum)

   add_executable(main src/main.cpp)
   target_link_libraries(main PRIVATE magic_enum::magic_enum)

模拟实现magic_enum

#include <array>
#include <string>
#include <utility>
#include <string_view>

template <typename E, E V>
constexpr auto PrettyName()
{
    std::string_view name{__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2};
    name.remove_prefix(name.find_last_of(" ") + 1);
    if (name.front() == '(') name.remove_prefix(name.size());
    return name;
}

template <typename E, E V>
constexpr bool IsValidEnum()
{
    return !PrettyName<E, V>().empty();
}

template <int... Seq>
constexpr auto MakeIntegerSequence(std::integer_sequence<int, Seq...>)
{
    return std::integer_sequence<int, (Seq)...>();
}

constexpr auto NormalIntegerSequence = MakeIntegerSequence(std::make_integer_sequence<int, 32>());

template <typename E, int... Seq>
constexpr size_t GetEnumSize(std::integer_sequence<int, Seq...>)
{
    constexpr std::array<bool, sizeof...(Seq)> valid{IsValidEnum<E, static_cast<E>(Seq)>()...};
    constexpr std::size_t count = [](decltype((valid)) v) constexpr noexcept->std::size_t
    {
        auto cnt = std::size_t{0};
        for (auto b : v) if (b) ++cnt;
        return cnt;
    }(valid);
    return count;
}

template <typename E, int... Seq>
constexpr auto GetAllValidValues(std::integer_sequence<int, Seq...>)
{
    constexpr std::size_t count = sizeof...(Seq);
    constexpr std::array<bool, count> valid{IsValidEnum<E, static_cast<E>(Seq)>()...};
    constexpr std::array<int, count> seq{Seq...};
    std::array<int, GetEnumSize<E>(NormalIntegerSequence)> values{};

    for (std::size_t i = 0, v = 0; i < count; ++i) if (valid[i]) values[v++] = seq[i];
    return values;
}

template <typename E, int... Seq>
constexpr auto GetAllValidNames(std::integer_sequence<int, Seq...>)
{
    constexpr std::array<std::string_view, sizeof...(Seq)> names{PrettyName<E, static_cast<E>(Seq)>()...};
    std::array<std::string_view, GetEnumSize<E>(NormalIntegerSequence)> validNames{};

    for (std::size_t i = 0, v = 0; i < names.size(); ++i) if (!names[i].empty()) validNames[v++] = names[i];
    return validNames;
}

template <typename E>
constexpr std::string_view Enum2String(E V)
{
    constexpr auto names = GetAllValidNames<E>(NormalIntegerSequence);
    constexpr auto values = GetAllValidValues<E>(NormalIntegerSequence);
    constexpr auto size = GetEnumSize<E>(NormalIntegerSequence);

    for (size_t i = 0; i < size; ++i) if (static_cast<int>(V) == values[i]) return names[i];
    return std::to_string(static_cast<int>(V));
}
#include "myenum.h"
#include <iostream>

enum class Color
{
    RED,
    GREEN,
    BLUE,
};

int main()
{
    Color c = Color::BLUE;
    std::cout << Enum2String(c) << std::endl;

    return 0;
}