C++静态设置结构设计:如何兼顾多类型、命名访问与编译期特性?
我尝试编写一个相对简单的Settings列表结构,但经过三次失败的设计迭代后,我确定自己缺少一个关键词来准确定位当前问题。
理想需求
- 每个设置拥有唯一Key,便于反(序列化)
- 列表中的每个设置是类成员或带有命名访问函数,支持IDE自动补全
- 每个设置可以是不同类型(String、uint8_t、uint16_t等)
- 每个设置拥有
const char*类型的名称 - 列表大小在编译期已知,支持随机访问迭代器,可使用数组下标访问
方案一
#define MAKE_UNION(struct_name, struct_def) \ struct __dummy_for_size__##struct_name struct_def; \ \ struct struct_name \ { \ union \ { \ struct struct_def; \ uint8_t as_list[sizeof(__dummy_for_size__##struct_name)]; \ }; \ }; MAKE_UNION(InputSettings, { uint8_t source; uint8_t channel; bool active; }) input_settings;
优点
- 输入
input_settings.s时,IDE可自动补全为input_settings.source - 可通过
input_settings.as_list[?]轻松实现数组下标访问
缺点
- 代码写法别扭,使用union可能处于未定义行为范畴,此前我甚至还用到了
void*强制转换... - 仅支持单字节类型,否则
as_list的下标访问会失效 - 无唯一Key,反(序列化)难以实现向前/向后兼容
- 设置没有C风格字符串名称
方案二
为简洁起见,省略大部分实现代码
struct ISetting{ const std::string unique_key; /*...*/ }; class SettingList { std::vector<std::unique_ptr<ISetting>> list; }; class ValueSetting : public ISetting {/*...*/}; class TextValueSetting : public ISetting{/*...*/}; SettingList input_settings; input_settings.list.push_back(TextValueSetting{"SOURCE", {"A", "B"}, 0}); input_settings.list.push_back(ValueSetting{"CHANNEL", 0, 15, 0}); input_settings.list.push_back(ValueSetting{"ACTIVE", 0, 1, 1}); auto get_source_setting = [&]{ return input_settings.list[0]; };
优点
- 唯一Key便于反(序列化)
- 设置带有名称
缺点
- 实现过于复杂
- 可能存在不必要的运行时多态。此外,由于实际开发中未使用RTTI,我必须通过枚举嵌入类型信息到接口中才能进行向下转换
- 使用vector但列表大小实际是已知的
- 设置无命名访问器,需手动编写并维护(如
get_source_settinglambda表达式)
方案三
struct Value { uint16_t val; }; struct TextValue { uint16_t val; std::span<std::string_view> values_names; }; std::array<std::pair<std::string, std::variant<Value, TextValue>>, 3> input_settings {{ {"SOURCE", TextValue{ /*... */ }}, {"CHANNEL", Value{ /*... */ }}, {"ACTIVE", Value{ /*... */ }}, //... //... }};
优点
- 拥有唯一Key
- 支持随机访问
- 可封装为类以实现便捷的
get_by_key(std::string)方法
缺点
- 无命名访问器
- 无随机访问迭代器
- 因std::variant的内存布局存在内存浪费(轻微问题)
即使是get_by_key(std::string)也会带来运行时性能开销。或许可以实现编译期的get<key_t>()?
核心问题
如何将不同类型混合到容器中,同时让每个元素自动拥有专属的命名getter/setter?所有元素在编译期已知,希望避免运行时开销。
内容的提问来源于stack exchange,提问作者Nebular Noise
相关产品推荐
相关产品推荐

