C++轻量级配置管理器升级版
通过重构此前的INI轻量级配置管理器,新的配置库的核心设计目标是提供一种统一的接口来操作多种格式(INI, JSON, XML
)的配置文件,同时保证类型安全和线程安全。它通过抽象基类、继承和多态机制,将不同格式的解析细节隐藏起来,为上层应用提供了简洁一致的get
和set
接口,同时支持更简洁的 []
运算符,使操作更加简单明了。
🧱 一、整体架构概览
整个库的架构清晰,主要由以下几个核心类构成,它们之间的协作关系如下图所示:
- ConfigException & ConfigErrorHandler:异常处理层
- ConfigNode:配置数据结构的核心容器
- ConfigParser:抽象解析器基类,定义通用接口
- IniParser / JsonParser / XmlParser:具体格式解析器
- ConfigManager:对外统一接口,封装线程安全与格式识别
⚡ 二、核心类详解
1️⃣ ConfigException & ConfigErrorHandler —— 错误处理机制
class ConfigException : public std::runtime_error
{
public:ConfigException(const String &msg, int line = -1): std::runtime_error(line < 0 ? msg : msg + " at line " + String(line)) {}
};
- 继承自 std::runtime_error,便于集成标准异常处理机制。
- 支持带行号的错误信息,便于调试。
class ConfigErrorHandler
{
public:static void logError(const String &message, bool throwEx = true){std::cerr << "Config Error: " << message << std::endl;if(throwEx) throw ConfigException(message);}
};
- 静态工具类,统一错误输出格式。
- 可选择是否抛出异常,提供灵活性(如日志记录后继续执行)。
2️⃣ ConfigNode —— 配置数据的核心容器
class ConfigNode
{
public:using ConfigConstIterator = std::map<String, ConfigNode>::const_iterator;ConfigNode() = default;ConfigNode(const String &value);ConfigNode(const ConfigNode &node); // 拷贝ConfigNode(ConfigNode&& other) noexcept; // 移动ConfigNode &operator[](const String &key); // 支持链式访问ConfigNode &operator=(const String &value); // 赋值重载// ... 其他赋值重载size_t erase(const String &key);ConfigNode &add(const String &key, const ConfigNode& value);template<typename T> T to() const; // 类型转换std::vector<String> keys() const;ConfigConstIterator find(const String &key) const;
private:std::map<String, ConfigNode> m_config; // 子节点String m_value; // 当前节点值(叶子)
};
-
🔍 核心特性:
-
树形结构: m_config 存储子节点,m_value 存储当前值,支持嵌套配置。
-
链式访问: config[“server”][“port”] = “8080”。
-
多种赋值方式: 支持 String、double、long、ConfigNode。
-
移动语义: 提升性能,避免深拷贝开销。
-
类型转换模板:
template<>
bool ConfigNode::to<bool>() const { ... } // 特化 bool 转换template<typename T>
inline T ConfigNode::to() const {return ::fromString<T>(m_value); // 依赖外部 fromString<T>
}
⚠️ 注意:依赖全局函数
fromString<T>
和toString
,在String.hpp
中实现,也可自行实现。
3️⃣ ConfigParser —— 抽象配置解析器
class ConfigParser
{
public:virtual bool load(const String &filePath) = 0;virtual String toString() = 0;virtual bool save(const String &filePath);virtual bool save();template<typename T> T get(...) const;template<typename T> void set(...);bool has(const String §ion) const;void remove(const String §ion, const String &key);ConfigNode &operator[](const String §ion);protected:String m_filePath;std::map<String, ConfigNode> m_config; // 所有节
};
📌 关键方法:
get<T>()
模板:
template<typename T>
inline T ConfigParser::get(const String §ion, const String &key, const T& defaultValue) const
{auto section_it = m_config.find(section);if(section_it != m_config.end()) {auto key_it = section_it->second.find(key);if(key_it != section_it->second.end()) {try {return ::fromString<T>(key_it->second.toString());} catch(...) {}}}return defaultValue; // 容错设计
}
-
支持默认值,避免程序崩溃。
-
内部 ·try-catch·,转换失败返回默认值,非异常驱动设计。
-
set<T>()
模板:
template<typename T>
inline void ConfigParser::set(const String §ion, const String &key, const T& value)
{if(m_config.find(section) == m_config.end()) {m_config.insert(std::make_pair(section, ConfigNode()));}m_config[section][key] = ::toString(value); // 依赖 toString
}
- 自动创建节(section),无需预先声明。
- 依赖
::toString(value)
,支持自定义类型。
4️⃣ IniParser —— INI 格式具体实现
class IniParser : public ConfigParser
{
public:bool load(const String &filePath) override;String toString() override;
private:void parseLine(const String &line, const String ¤tSection, int lineNumber);
};
📄 INI 文件格式示例:
[Database]
host = localhost
port = 5432
ssl = true[Server]
port = 8080
debug = false
🔍 load() 解析流程:
- 逐行读取
- 跳过空行/注释(# 或 ;)
- [Section] → 设置 currentSection
- key = value → 调用 parseLine()
void IniParser::parseLine(const String &line, const String ¤tSection, int lineNumber)
{size_t eqPos = line.find('=');if(eqPos != String::npos) {String key = trim(line.substr(0, eqPos));String value = trim(line.substr(eqPos + 1));if(!currentSection.empty()) {m_config[currentSection][key] = value;} else {throw ConfigException("No section for key '" + key + "'", lineNumber);}} else {throw ConfigException("Invalid line", lineNumber);}
}
📝 toString() 生成格式化输出:
String IniParser::toString()
{String result;for(const auto& section : sections()) {result += "[" + section + "]\n";for(const auto& key : keys(section)) {result += key + " = " + m_config[section][key].to<String>() + "\n";}result += "\n";}return result;
}
✅ 支持自动格式化、节间空行,提高可读性。
5️⃣ JsonParser / XmlParser (待实现)
实现文件解析接口 load
和文件格式化输出接口 toString
。
bool load(const String &filePath) { ... }
String toString() { ... }
🚀 扩展建议:可集成 nlohmann/json 或 pugixml 库实现完整功能。
6️⃣ ConfigManager —— 统一入口 & 线程安全网关
class ConfigManager
{
public:ConfigManager(const String &filePath);bool load(const String &filePath);template<typename T> bool set(...);template<typename T> T get(...) const;bool save();std::vector<String> sections();ConfigNode &operator[](const String §ion);private:mutable std::mutex m_mutex;std::shared_ptr<ConfigParser> m_configParser;
};
🔄 自动格式识别:
bool ConfigManager::load(const String &filePath)
{String file_type = filePath.subAfter('.').lower();if(file_type == "json") m_configParser.reset(new JsonParser());else if(file_type == "xml") m_configParser.reset(new XmlParser());else m_configParser.reset(new IniParser()); // 默认INIreturn m_configParser->load(filePath);
}
📎链式访问
ConfigNode &operator[](const String §ion);
- 通过实现
ConfigManager、ConfigParser、ConfigNode
的[]
的运算符实现了快捷方便的链式访问
🧪 三、使用示例
#include "src/ConfigManager.hpp"int main()
{ConfigManager config;// 1. 加载配置文件if(!config.load("app.conf")){std::cerr << "Failed to load config file." << std::endl;// 可以选择创建一个默认配置config.set("Database", "host", "localhost");config.set("Database", "port", 5432);config.set("Database", "username", "user");config.set("Database", "password", "pass");config.set("UI", "theme", "dark");config.set("UI", "language", "en");config.set("UI", "auto_save", true);config.save("app.conf"); // 保存默认配置std::cout << "Created default config file 'app.conf'." << std::endl;}// 2. 读取配置值String dbHost = config.get<String>("Database", "host", "default_host");int dbPort = config.get<int>("Database", "port", 3306); // 默认端口3306String theme = config.get<String>("UI", "theme", "light");bool autoSave = config.get<bool>("UI", "auto_save", false);std::cout << "Database Host: " << dbHost << std::endl;std::cout << "Database Port: " << dbPort << std::endl;std::cout << "UI Theme: " << theme << std::endl;std::cout << "Auto Save: " << (autoSave ? "Enabled" : "Disabled") << std::endl;// 3. 修改配置值config.set("Database", "host", "newhost.example.com");config.set("UI", "language", "zh");// 使用[]方式修改config["UI"]["theme"] = "light";// 4. 添加新的配置项config.set("Logging", "level", "DEBUG");config.set("Logging", "file", "app.log");// 使用[]方式添加配置项config["System"]["version"] = "1.0.0";// 5. 保存修改后的配置if(!config.save()){std::cerr << "Failed to save config file." << std::endl;}else{std::cout << "Config file saved successfully." << std::endl;}// 6. 检查存在性if(config.has("Logging")){std::cout << "Section 'Logging' exists." << std::endl;}if(config.has("UI", "language")){std::cout << "Key 'language' exists in section 'UI'." << std::endl;}// 7. 获取所有节和键std::cout << "\nAll Sections:" << std::endl;std::cout << config.toString();return 0;
}
运行结果:
项目源码