如何存储C++中编译期已知、大小固定的小型层级静态常量数据?
C++ 编译期静态多维常量数据集最优实现方案
针对编译期已知、无需实例化、支持层级与动态查找的静态多维常量存储需求,C++11及以上版本最优实现方案如下:
方案核心思路
放弃运行时容器(如unordered_map),完全使用编译期常量语法实现,零运行时开销,完全匹配全部需求点。
完整实现代码(仅需头文件即可使用)
#pragma once #include <array> #include <cstdint> #include <string_view> // 用命名空间封装所有常量,避免全局命名污染 namespace MidiEventTypes { // 1. 定义每个事件的通用属性结构体,可任意扩展字段 struct EventAttr { std::uint8_t event_value; // 事件对应的数值 std::uint8_t required_bytes; // 需要读取的字节数 std::string_view event_name; // 示例扩展字段:事件名 // 可按需添加更多属性,比如事件描述、处理函数指针等 }; // 2. 定义所有事件的数值常量,仅需定义一次 inline constexpr std::uint8_t NOTE_OFF = 8; inline constexpr std::uint8_t NOTE_ON = 9; inline constexpr std::uint8_t KEY_AFTERTOUCH = 10; inline constexpr std::uint8_t CONTROL_CHANGE = 11; inline constexpr std::uint8_t PROGRAM_CHANGE = 12; inline constexpr std::uint8_t CHANNEL_AFTERTOUCH = 13; inline constexpr std::uint8_t PITCH_WHEEL_CHANGE = 14; // 3. 编译期映射表,所有数据编译时确定,运行时只读,无多副本 inline constexpr std::array<EventAttr, 7> EVENT_META = {{ {NOTE_OFF, 2, "NOTE_OFF"}, {NOTE_ON, 2, "NOTE_ON"}, {KEY_AFTERTOUCH, 2, "KEY_AFTERTOUCH"}, {CONTROL_CHANGE, 2, "CONTROL_CHANGE"}, {PROGRAM_CHANGE, 1, "PROGRAM_CHANGE"}, {CHANNEL_AFTERTOUCH, 1, "CHANNEL_AFTERTOUCH"}, {PITCH_WHEEL_CHANGE, 2, "PITCH_WHEEL_CHANGE"} }}; // 4. 编译期查找函数,支持常量和变量查询 constexpr EventAttr get_event_attr(std::uint8_t event_val) { for (const auto& meta : EVENT_META) { if (meta.event_value == event_val) { return meta; } } // 异常分支可按需处理,C++23可用std::unreachable()触发编译错误 return {0, 0, "UNKNOWN_EVENT"}; } // 5. 快捷查询函数,直接返回需要的字节数 constexpr std::uint8_t GetRequiredBytesByEventType(std::uint8_t event_val) { return get_event_attr(event_val).required_bytes; } // 可选:实现类似MidiEventTypes::NOTE_OFF::NUM_BYTES的层级访问语法 namespace _impl { template<std::uint8_t Val> inline constexpr EventAttr cached_attr = get_event_attr(Val); } inline constexpr auto NOTE_OFF_META = _impl::cached_attr<NOTE_OFF>; inline constexpr auto NOTE_ON_META = _impl::cached_attr<NOTE_ON>; // 其他事件的元数据可按同样方式定义,也可用宏批量生成避免重复代码 }
用法示例
1. 基础常量比较
std::uint8_t test_val = 8; if (test_val == MidiEventTypes::NOTE_OFF) { // 业务逻辑 }
2. 静态层级访问属性
std::cout << "NOTE_OFF事件需要读取的字节数:" << MidiEventTypes::NOTE_OFF_META.required_bytes << std::endl;
3. 按运行时变量动态查询
std::uint8_t test_val = 8; std::uint8_t bytes_needed = MidiEventTypes::GetRequiredBytesByEventType(test_val); // 也可以直接获取完整属性 auto event_meta = MidiEventTypes::get_event_attr(test_val); std::cout << "事件名:" << event_meta.event_name << ",需要字节数:" << event_meta.required_bytes << std::endl;
4. 编译期求值(零运行时开销)
// 编译阶段就计算出字节数,运行时直接作为常量使用 constexpr std::uint8_t note_on_bytes = MidiEventTypes::GetRequiredBytesByEventType(MidiEventTypes::NOTE_ON);
方案优势
完全匹配全部需求:
- 所有数据编译期确定,永不修改,无运行时初始化开销
- 仅需引入头文件即可使用,全局只有一份数据副本,无冗余
- 事件数值仅定义一次,所有引用都用常量名,修改只需改一处,不会出现不同步问题
- 支持扩展任意多的属性,只需修改
EventAttr结构体即可 - 同时支持静态常量访问和运行时变量查找,性能远高于运行时哈希表
- 类型安全,无需强制类型转换
可选C++20极简层级语法实现
如果编译环境支持C++20,想要完全匹配MidiEventTypes::NOTE_OFF::NUM_BYTES的伪代码写法,可以用嵌套常量结构体实现:
namespace MidiEventTypes { // 每个事件对应一个嵌套结构体,直接定义所有属性 struct NOTE_OFF { static constexpr std::uint8_t VALUE = 8; static constexpr std::uint8_t NUM_BYTES = 2; static constexpr std::string_view NAME = "NOTE_OFF"; }; struct NOTE_ON { static constexpr std::uint8_t VALUE = 9; static constexpr std::uint8_t NUM_BYTES = 2; static constexpr std::string_view NAME = "NOTE_ON"; }; // 其他事件同理定义 // 仍可保留动态查找功能,用编译期数组映射即可 inline constexpr std::array<std::uint8_t, 256> BYTES_LOOKUP = [](){ std::array<std::uint8_t, 256> arr{}; arr[NOTE_OFF::VALUE] = NOTE_OFF::NUM_BYTES; arr[NOTE_ON::VALUE] = NOTE_ON::NUM_BYTES; // 其他事件赋值 return arr; }(); constexpr std::uint8_t GetRequiredBytesByEventType(std::uint8_t val) { return BYTES_LOOKUP[val]; } }
使用时完全符合伪代码要求:
if (test_val == MidiEventTypes::NOTE_OFF::VALUE) { std::cout << MidiEventTypes::NOTE_OFF::NUM_BYTES << std::endl; }
内容的提问来源于stack exchange,提问作者Nick
相关产品推荐
相关产品推荐

