首先,有两种方法:使用命名空间和字符串常量与使用 enum class
和辅助函数。
表格直观展示
特性 | 使用命名空间和字符串常量 | 使用 enum class 和辅助函数 |
---|---|---|
类型安全性 | 低 - 编译器无法检查字符串有效性,运行时发现错误 | 高 - 编译期类型检查,非法类型在编译期即可发现 |
性能 | 低 - 字符串比较时间复杂度为 O(n),逐字符比较有开销 | 高 - 枚举值比较时间复杂度为 O(1),查找效率高,平均查找为 O(1) |
可维护性 | 中 - 易于理解和使用,但字符串容易拼写错误 | 高 - 易读易维护,枚举类型和映射关系可以方便管理和扩展 |
代码简洁性 | 中 - 可能需要反复访问命名空间中的字符串常量 | 高 - 代码简洁且易读,枚举结合辅助函数方便处理 |
安全性 | 中 - 字符串误输入导致运行时错误 | 高 - 通过类型检查避免运行时崩溃 |
易用性 | 中 - 需要记住和管理字符串常量,容易犯拼写错误 | 高 - 枚举值可以通过智能提示和自动补全,大幅减少错误 |
代码展示
使用命名空间和字符串常量
优点:
- 直观和易于理解:字符串表示类型或状态非常直观,容易理解和使用。
- 与外部系统交互方便:许多外部系统或配置文件使用字符串表示状态或类型,直接使用字符串便于与这些系统交互。
缺点:
- 类型不安全:任何字符串都可以传入,编译器无法检查其有效性,拼写错误会导致运行时错误。
- 性能开销较大:字符串比较和逐字符检查的时间复杂度为 O(n),性能较低,尤其是在处理长字符串时。
- 可维护性差:字符串常量需要在代码中多次引用,拼写错误不易发现,维护困难。
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
和辅助函数
优点:
- 类型安全:编译器进行类型检查,非法类型在编译期即可发现,避免运行时错误。
- 性能高效:枚举值比较时间复杂度为 O(1),使用哈希表进行字符串到枚举的映射查找平均为 O(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.
解决方法:
- 下载并包含
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 - 将
magic_enum.hpp
头文件放在你的项目的合适目录中,例如include
目录。 - 编译
g++ -std=c++17 -Iinclude main.cpp -o main
如果你使用 CMake 进行项目构建,可以通过 FetchContent
将 megic_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;
}