通过重构此前的INI轻量级配置管理器,新的配置库的核心设计目标是提供一种统一 的接口来操作多种格式(INI, JSON, XML
)的配置文件,同时保证类型安全 和线程安全 。它通过抽象基类、继承和多态机制,将不同格式的解析细节隐藏起来,为上层应用提供了简洁一致的get
和set
接口,同时支持更简洁的 []
运算符,使操作更加简单明了。
🧱 一、整体架构概览
整个库的架构清晰,主要由以下几个核心类构成,它们之间的协作关系如下图所示:
聚合 被抛出 包含 1 * 递归包含 (树形结构) 1 * ConfigException +ConfigException(String& msg, int line) <<static>> ConfigErrorHandler +logError(message, throwEx) ConfigNode -m_value: String -m_config: map<String, ConfigNode> ... +to() : T +operator[](key) +add(key, value) +keys() ConfigParser #m_filePath: String #m_config: map<String, ConfigNode> ... +load(filePath) +toString() +get(section, key, defaultValue) : T +set(section, key, value) +sections() IniParser +load(filePath) +toString() -parseLine(line, currentSection, lineNumber) JsonParser +load(filePath) +toString() XmlParser +load(filePath) +toString() ConfigManager -m_mutex: mutex -m_configParser: shared_ptr<ConfigParser> ... +get(section, key, defaultValue) : T +set(section, key, value) +save(filePath) +operator[](section)
- ConfigException & ConfigErrorHandler:异常处理层
- ConfigNode:配置数据结构的核心容器
- ConfigParser:抽象解析器基类,定义通用接口
- IniParser / JsonParser / XmlParser:具体格式解析器
- ConfigManager:对外统一接口,封装线程安全与格式识别
⚡ 二、核心类详解
1️⃣ ConfigException & ConfigErrorHandler ------ 错误处理机制
cpp
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,便于集成标准异常处理机制。
- 支持带行号的错误信息,便于调试。
cpp
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 ------ 配置数据的核心容器
cpp
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。
-
移动语义: 提升性能,避免深拷贝开销。
-
类型转换模板:
cpp
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 ------ 抽象配置解析器
cpp
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>()
模板:
cpp
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>()
模板:
cpp
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 格式具体实现
cpp
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 文件格式示例:
ini
[Database]
host = localhost
port = 5432
ssl = true
[Server]
port = 8080
debug = false
🔍 load() 解析流程:
- 逐行读取
- 跳过空行/注释(# 或 ;)
-
Section\] → 设置 currentSection
cpp
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() 生成格式化输出:
cpp
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
。
cpp
bool load(const String &filePath) { ... }
String toString() { ... }
🚀 扩展建议:可集成 nlohmann/json 或 pugixml 库实现完整功能。
6️⃣ ConfigManager ------ 统一入口 & 线程安全网关
cpp
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;
};
🔄 自动格式识别:
cpp
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()); // 默认INI
return m_configParser->load(filePath);
}
📎链式访问
cpp
ConfigNode &operator[](const String §ion);
- 通过实现
ConfigManager、ConfigParser、ConfigNode
的[]
的运算符实现了快捷方便的链式访问
🧪 三、使用示例
cpp
#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); // 默认端口3306
String 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;
}
运行结果: