今天分享一个简易的C++配置文件读取器。大概能读取的格式如下:
[group1]
arg1=1
arg2=2.0
arg3=D:/123
#我是一行注释
[group2]
arg1=woshiniyeye
大概说下几个点,不懂的自行百度(大佬当我放屁,没啥高级货):
- 完全使用标准库实现(说的屁话)
- 单例模式
- call once初始化
- 参数可以给默认值,防止没读取到
- 值中间不能有空格
- 组名不能重复,每个组当中的节点名不能重复
- 值不支持中文
直接上代码,先来头文件
#pragma once
#include <map>
#include <string>
#include <mutex>
#include <memory>
/*****读取具有如下形式的配置文件****************
* [group1]
* arg_float=0.2
* arg_int=1
* arg_string=12345
* #我是一行注释
* [group2]
* arg_666 = 我是你大哥
* ....
*
* 每个中括号表示一个section,不能重复出现
* =号左边的字段,在每个section中不能重复出现
* setcion、字段、值不能出现空格和中文字符
* # 或 ; 代表注释行,只能一行一行注释
******************************************/
class Config
{
private:
std::string escapeString(const std::string& src);
template<typename T>
T getParam(const std::map<std::string, std::string>& params, const std::string param_name, T init_val);
std::map<std::string, std::map<std::string, std::string>> m_params;
void getGroup(std::map<std::string, std::string>& param,std::string name);
private:
Config() = default;
Config(const Config&) = delete;
Config& operator=(const Config&) = delete;
static std::shared_ptr<Config> m_instance;
public:
void readConfig(const char* filename);
static std::shared_ptr<Config> instance();//实例接口
public:
float arg_float;
int arg_int;
std::string arg_string;
std::string arg_666;
};
然后是CPP实现
#include "config.h"
#include <istream>
#include <iostream>
#include <fstream>
#include <sstream>
#define printf(str,...) printf(str,__VA_ARGS__);printf("\n");
void config_expfunc(const char* expr, const char* func, const char* file, int line)
{
printf("%s %s %s %d", expr, func, file, line);
throw std::exception("解析错误");
}
#define CONFIG_Assert( expr ) do { if(!!(expr)) ; else config_expfunc(#expr,__FUNCTION__, __FILE__, __LINE__ ); } while(0)
std::string Config::escapeString(const std::string& src)
{
std::string dst;
for (std::size_t i = 0; i < src.size(); ++i)
if (src[i] > ' ' && src[i] <= 'z')
dst += src[i];
return dst;
}
void Config::getGroup(std::map<std::string, std::string>& param,std::string name)
{
param.clear();
if (m_params.find(name) != m_params.end())
{
param = m_params[name];
}
}
template<typename T>
T Config::getParam(const std::map<std::string, std::string>& params, const std::string param_name, T init_val)
{
std::map<std::string, std::string>::const_iterator it = params.find(param_name);
if (it != params.end()) {
std::stringstream ss(it->second);
ss >> init_val;
printf("解析配置文件时,[%s] = %s", param_name.c_str(), ss.str().c_str());
} else {
std::stringstream ss;
ss << init_val;
printf("解析配置文件时,未设置 [%s],使用默认值 %s", param_name.c_str(), ss.str().c_str());
}
return init_val;
}
template<>
std::string Config::getParam<std::string>(const std::map<std::string, std::string>& params, const std::string param_name, std::string init_val)
{
std::map<std::string, std::string>::const_iterator it = params.find(param_name);
if (it != params.end()) {
init_val = it->second;
printf("解析配置文件时,[%s] = %s", param_name.c_str(), init_val.c_str());
} else
{
printf("解析配置文件时,未设置 [%s],使用默认值 %s", param_name.c_str(), init_val.c_str());
}
return init_val;
}
void Config::readConfig(const char* filename)
{
//print_log(LOG_INFO, "loading config");
m_params.clear();
std::filebuf in;
if (!in.open(filename, std::ios::in))
{
//print_log(LOG_ERROR, "failed to read config file,全部使用默认值");
printf("failed to read config file,全部使用默认值");
} else
{
try
{
std::string last_section;
std::istream ifile(&in);
for (std::string line; std::getline(ifile, line);) {
line = escapeString(line);
if (line.empty()) continue;
switch (line[0]) {
case '\0': break;
case '#': break;
case ';': break;
case '[': {
const size_t layer_type_size = line.find("]") - 1;
CONFIG_Assert(layer_type_size < line.size());
std::string layer_type = line.substr(1, layer_type_size);
CONFIG_Assert(m_params.find(layer_type) == m_params.end());
m_params[layer_type] = std::map<std::string, std::string>(); //读取分组
last_section = layer_type;
}
break;
default:
//读取每个分组里的节点内容
const size_t separator_index = line.find('=');
CONFIG_Assert(separator_index < line.size());
CONFIG_Assert(!m_params.empty());
if (separator_index != std::string::npos) {
std::string name = line.substr(0, separator_index);
std::string value = line.substr(separator_index + 1, line.size() - (separator_index + 1));
name = escapeString(name);
value = escapeString(value);
if (name.empty() || value.empty()) continue;
auto& section = m_params[last_section];
CONFIG_Assert(section.find(name) == section.end());
section[name] = value;
}
break;
}
}
} catch (std::exception& msg)
{
printf("配置文件解析失败 详细信息:%s", msg.what());
throw std::exception("配置文件解析失败");
} catch (...)
{
printf("配置文件解析失败");
throw std::exception("配置文件解析失败");
}
}
printf("读取配置文件节点数据");
//section
std::map<std::string, std::string> param;
getGroup(param,"group1");
arg_float = getParam(param, "arg_float", 0.2);
arg_int = getParam(param, "arg_int", 1);
arg_string = getParam<std::string>(param, "arg_string", "12345");
getGroup(param, "group2");
arg_666 = getParam<std::string>(param, "arg_666", "我是你大哥");
printf( "配置文件读取成功");
}
template class std::unique_ptr<Config>;
std::shared_ptr<Config> Config::m_instance;
std::shared_ptr<Config> Config::instance()
{
static std::once_flag once_flag;
std::call_once(once_flag, [&]()
{
m_instance.reset(new Config);
m_instance->readConfig("config.ini");
});
return m_instance;
}
最后是一个测试文件
#include <iostream>
#include "config.h"
using namespace std;
int main()
{
auto& config = *Config::instance();
cout << config.arg_666 << endl;
}
如果读取配置文件失败的话,会全部使用默认值,
如果读取配置文件成功的话,是这样的:
说明一下
- -其实这段代码是我从opencv源码里面拿出来改的
- 上面的CONFIG_Assert 、printf宏完全是为了方便贴代码展示改的
- 细心的朋友发现了有个print_log注释,那是另外一个简易日志库,叫做zlog,可以gayhub搜搜,可以的话,我下次再写一篇简易日志库凑凑博客数。