结构体变量遍历Getter及结构体数组配置持久化技术问询
嘿,针对你提到的两个C++问题,我给你整理了一套可扩展的解决方案,完全适配结构体后续扩展的需求,咱们一步步来看~
解决方案
一、实现可遍历的结构体成员Getter
因为结构体成员会随扩展增加,硬编码每个成员的Getter肯定不现实,咱们可以用元编程+成员信息列表的方式来实现可扩展的遍历Getter。核心思路是把结构体的成员指针、名称、类型处理逻辑打包成元组,然后遍历这个元组来统一访问每个成员。
举个贴合你给出的struct S的实际例子:
#include <iostream> #include <tuple> #include <vector> #include <string> #include <sstream> struct S { int a; float b; std::vector<float> c; }; // 定义成员信息模板,打包成员指针、名称和访问逻辑 template<typename T, typename MemType> struct MemberInfo { MemType S::*member_ptr; // 指向结构体成员的指针 const char* name; // 成员名称 // 通用取值函数 MemType get(const S& obj) const { return obj.*member_ptr; } }; // 构建结构体S的成员列表,后续扩展成员只需要在这里加新项即可 constexpr auto S_Members = std::make_tuple( MemberInfo<S, int>{&S::a, "a"}, MemberInfo<S, float>{&S::b, "b"}, MemberInfo<S, std::vector<float>>{&S::c, "c"} ); // 遍历元组的辅助函数,处理不同类型的成员输出 template<typename Tuple, std::size_t... Is> void iterate_members(const S& obj, const Tuple& tuple, std::index_sequence<Is...>) { (void)std::initializer_list<int>{ (std::cout << std::get<Is>(tuple).name << ": " << [&]() -> std::string { const auto& val = std::get<Is>(tuple).get(obj); // 针对vector做特殊格式化,普通类型直接转字符串 if constexpr (std::is_same_v<decltype(val), std::vector<float>>) { std::ostringstream oss; oss << "["; for (size_t i = 0; i < val.size(); ++i) { if (i > 0) oss << ", "; oss << val[i]; } oss << "]"; return oss.str(); } else { return std::to_string(val); } }() << std::endl, 0)... }; } // 对外暴露的遍历打印函数 void print_all_members(const S& obj) { iterate_members(obj, S_Members, std::make_index_sequence<std::tuple_size_v<decltype(S_Members)>>{}); } // 测试代码 int main() { S obj{10, 3.14f, {1.0f, 2.0f, 3.0f}}; print_all_members(obj); return 0; }
后续结构体新增成员时,只需要在S_Members元组里添加对应的MemberInfo项,遍历逻辑完全不用修改,完美适配扩展需求。
二、配置的保存与加载功能
基于上面的成员列表思路,咱们可以把序列化(保存)和反序列化(加载)逻辑绑定到成员信息上,这样扩展结构体时同样只需要更新成员列表,不用改动核心的保存/加载代码。
1. 保存配置到文件
保存时采用行式存储:每行格式为成员名: 值,对于vector这类容器,先存储元素个数,再依次存储每个元素(比如c: 3 1.0 2.0 3.0)。
#include <fstream> // 给MemberInfo添加保存方法 template<typename T, typename MemType> struct MemberInfo { MemType S::*member_ptr; const char* name; MemType get(const S& obj) const { return obj.*member_ptr; } // 保存单个成员到文件 void save(std::ofstream& file, const S& obj) const { file << name << ": "; const auto& val = get(obj); if constexpr (std::is_same_v<MemType, std::vector<float>>) { file << val.size(); for (const auto& elem : val) { file << " " << elem; } } else { file << val; } file << std::endl; } }; // 遍历保存所有成员的辅助函数 template<typename Tuple, std::size_t... Is> void iterate_save(std::ofstream& file, const S& obj, const Tuple& tuple, std::index_sequence<Is...>) { (void)std::initializer_list<int>{(std::get<Is>(tuple).save(file, obj), 0)...}; } // 对外暴露的批量保存函数 void save_config(const S* config_array, size_t count, const std::string& filename) { std::ofstream file(filename); if (!file.is_open()) { std::cerr << "Failed to open file for saving!" << std::endl; return; } // 先存储配置实例的总个数 file << "config_count: " << count << std::endl; // 逐个保存每个配置实例 for (size_t i = 0; i < count; ++i) { file << "--- config_" << i << " ---" << std::endl; iterate_save(file, config_array[i], S_Members, std::make_index_sequence<std::tuple_size_v<decltype(S_Members)>>{}); } }
2. 从文件加载配置
加载时先读取配置实例的总个数,再逐个实例读取成员值;对于vector类型,先读取元素个数,再读取对应数量的元素。
// 给MemberInfo添加加载方法 template<typename T, typename MemType> struct MemberInfo { // ... 保留之前的成员和方法 ... // 从文件加载单个成员 void load(std::ifstream& file, S& obj) const { std::string line; // 跳过无关行,找到当前成员的配置行 while (std::getline(file, line)) { if (line.find(name) == 0) { break; } } size_t colon_pos = line.find(':'); if (colon_pos == std::string::npos) return; std::istringstream value_stream(line.substr(colon_pos + 1)); auto& val = obj.*member_ptr; if constexpr (std::is_same_v<MemType, std::vector<float>>) { size_t size; value_stream >> size; val.resize(size); for (size_t i = 0; i < size; ++i) { value_stream >> val[i]; } } else { value_stream >> val; } } }; // 遍历加载所有成员的辅助函数 template<typename Tuple, std::size_t... Is> void iterate_load(std::ifstream& file, S& obj, const Tuple& tuple, std::index_sequence<Is...>) { (void)std::initializer_list<int>{(std::get<Is>(tuple).load(file, obj), 0)...}; } // 对外暴露的批量加载函数 void load_config(S*& config_array, size_t& count, const std::string& filename) { std::ifstream file(filename); if (!file.is_open()) { std::cerr << "Failed to open file for loading!" << std::endl; return; } // 读取配置实例总个数 std::string line; while (std::getline(file, line)) { if (line.find("config_count:") == 0) { std::istringstream iss(line.substr(line.find(':') + 1)); iss >> count; break; } } // 分配内存存储配置数组 config_array = new S[count]; // 逐个加载每个配置实例 for (size_t i = 0; i < count; ++i) { // 找到当前配置实例的分割线 while (std::getline(file, line)) { if (line.find("--- config_" + std::to_string(i) + " ---") != std::string::npos) { break; } } iterate_load(file, config_array[i], S_Members, std::make_index_sequence<std::tuple_size_v<decltype(S_Members)>>{}); } }
使用说明
- 程序退出前调用
save_config,传入你的结构体数组、数组长度和保存路径即可完成配置持久化; - 程序启动时调用
load_config,传入结构体数组指针的引用和长度的引用,加载完成后就能直接使用配置; - 后续结构体新增成员,仅需在
S_Members元组里添加对应的MemberInfo项,保存和加载逻辑会自动适配,无需修改核心代码。
内容的提问来源于stack exchange,提问作者a concerned citizen
相关产品推荐
相关产品推荐

