如何在C++中解析包含不同类型数据的配置文件?
问题描述
有如下配置文件内容:
name=Ben age=21 score=4.65
需要解析该文件并初始化自定义类InpData的字段,要求不在解析过程中手动指定每个字段的数据类型。
自定义类定义:
class InpData { public: // 需将成员设为public或提供访问接口,否则无法直接赋值 std::string name; int age; double score; };
希望避免这种硬编码判断字段的写法:
InpData mydata; std::string line, key, value; std::ifstream file("config.ini"); while (std::getline(file, line)) { std::istringstream iss(line); if (std::getline(iss, key, '=')) { std::getline(iss, value); if (key == "name") mydata.name = value; else if (key == "age") mydata.age = std::stoi(value); else if (key == "score") mydata.score = std::stod(value); } }
简单来说,想要一个类似std::map但能存储不同类型数据的容器,曾考虑过std::variant或std::tuple但不符合需求,想了解合适的实现方式。
解决方案
方法1:用std::unordered_map<std::string, std::any>存储类型化值
C++17引入的std::any可存储任意类型的值,配合哈希表容器实现"键-任意类型值"的映射,之后再将值赋值给类成员:
#include <iostream> #include <fstream> #include <sstream> #include <unordered_map> #include <any> #include <string> class InpData { public: std::string name; int age; double score; }; int main() { std::unordered_map<std::string, std::any> configMap; std::ifstream file("config.ini"); std::string line, key, valueStr; // 解析配置文件到map while (std::getline(file, line)) { std::istringstream iss(line); if (std::getline(iss, key, '=') && std::getline(iss, valueStr)) { // 提前根据字段名映射类型转换 if (key == "name") { configMap[key] = valueStr; } else if (key == "age") { configMap[key] = std::stoi(valueStr); } else if (key == "score") { configMap[key] = std::stod(valueStr); } } } // 初始化InpData对象 InpData mydata; mydata.name = std::any_cast<std::string>(configMap["name"]); mydata.age = std::any_cast<int>(configMap["age"]); mydata.score = std::any_cast<double>(configMap["score"]); // 测试输出 std::cout << "Name: " << mydata.name << "\nAge: " << mydata.age << "\nScore: " << mydata.score << std::endl; return 0; }
这种方式将类型转换逻辑与对象赋值逻辑分离,比直接在解析循环里赋值更清晰,且map可复用。
方法2:自定义类型感知的配置容器
封装一个自定义容器,内部维护字段名到类型转换函数的映射,支持直接给目标类赋值:
#include <iostream> #include <fstream> #include <sstream> #include <unordered_map> #include <functional> #include <string> class InpData { public: std::string name; int age; double score; }; class ConfigContainer { private: std::unordered_map<std::string, std::function<void(const std::string&, InpData&)>> fieldSetters; public: ConfigContainer() { // 提前注册每个字段的赋值函数 fieldSetters["name"] = [](const std::string& val, InpData& data) { data.name = val; }; fieldSetters["age"] = [](const std::string& val, InpData& data) { data.age = std::stoi(val); }; fieldSetters["score"] = [](const std::string& val, InpData& data) { data.score = std::stod(val); }; } // 解析配置并直接赋值给InpData对象 void parseAndAssign(const std::string& filePath, InpData& data) { std::ifstream file(filePath); std::string line, key, value; while (std::getline(file, line)) { std::istringstream iss(line); if (std::getline(iss, key, '=') && std::getline(iss, value)) { auto it = fieldSetters.find(key); if (it != fieldSetters.end()) { it->second(value, data); } } } } }; int main() { InpData mydata; ConfigContainer config; config.parseAndAssign("config.ini", mydata); std::cout << "Name: " << mydata.name << "\nAge: " << mydata.age << "\nScore: " << mydata.score << std::endl; return 0; }
该容器将字段的类型转换逻辑封装在内,解析时只需根据键找到对应赋值函数,无需写一堆if-else,后续新增字段仅需在构造函数中注册新的setter。
方法3:利用编译期反射(C++20及以上)
若使用C++20或更高版本,可借助编译期反射特性(如第三方库Boost.PFR)自动遍历类成员,实现近乎无硬编码的解析:
#include <iostream> #include <fstream> #include <sstream> #include <unordered_map> #include <string> #include <boost/pfr.hpp> class InpData { public: std::string name; int age; double score; }; // 定义字段名到成员索引的映射 const std::unordered_map<std::string, size_t> fieldIndexMap = { {"name", 0}, {"age", 1}, {"score", 2} }; int main() { InpData mydata; std::ifstream file("config.ini"); std::string line, key, value; while (std::getline(file, line)) { std::istringstream iss(line); if (std::getline(iss, key, '=') && std::getline(iss, value)) { auto it = fieldIndexMap.find(key); if (it != fieldIndexMap.end()) { // 利用Boost.PFR根据索引访问成员并赋值 size_t idx = it->second; if (idx == 0) { boost::pfr::get<0>(mydata) = value; } else if (idx == 1) { boost::pfr::get<1>(mydata) = std::stoi(value); } else if (idx == 2) { boost::pfr::get<2>(mydata) = std::stod(value); } } } } std::cout << "Name: " << mydata.name << "\nAge: " << mydata.age << "\nScore: " << mydata.score << std::endl; return 0; }
这种方式无需手动编写每个成员的赋值逻辑,仅需维护字段名与索引的映射,新增字段时更新映射和对应类型转换即可,进一步减少重复代码。
内容的提问来源于stack exchange,提问作者DanPolev
相关产品推荐
相关产品推荐

