You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 08:05:32