
配置文件的读取是每个程序必备的功能配置文件的格式多种多样例如ini格式、json格式、xml格式等。其中属ini格式最为简单且应用广泛。2.ini格式语法注释内容采用“#”或者“;”开头。配置是由一系列的section组成每个section就是一个关联的配置块section使用[]包含起来。每个section下配置的是具体的配置项每个配置项是使用“”分隔的key-value对。下面让我们来看一个简单的示例假设我们有一个配置文件demo.cfg它的内容如下所示。[server]ip 127.0.0.1port 8088上面的配置内容中有一个server的配置节在这个配置节里有两个配置项它们分别是ip和portip的值为127.0.0.1port的值为8088。3.配置读取知道了ini格式语法之后就可以根据语法规则来读取配置文件内容了春哥这里实现了一个非常精简易用的版本源代码文件config.hpp的内容如下。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129#pragma once#include fstream#include functional#include string#include unordered_mapnamespaceConfig {classIni {public:voidDump(std::functionvoid(conststd::string,conststd::string,conststd::string) deal) {auto iter cfg_.begin();while(iter ! cfg_.end()) {auto kv_iter iter-second.begin();while(kv_iter ! iter-second.end()) {deal(iter-first, kv_iter-first, kv_iter-second);kv_iter;}iter;}}boolLoad(std::string file_name) {if(file_name )returnfalse;std::ifstream in;std::string line;in.open(file_name.c_str());if(not in.is_open())returnfalse;while(getline(in, line)) {std::string section, key, value;if(not parseLine(line, section, key, value)) {continue;}setSectionKeyValue(section, key, value);}returntrue;}voidGetStrValue(conststd::string section,conststd::string key, std::string value, std::string default_value) {value default_value;if(cfg_.find(section) cfg_.end()) {return;}if(cfg_[section].find(key) cfg_[section].end()) {return;}value cfg_[section][key];}voidGetIntValue(conststd::string section,conststd::string key, int64_t value, int64_t default_value) {value default_value;if(cfg_.find(section) cfg_.end()) {return;}if(cfg_[section].find(key) cfg_[section].end()) {return;}value atol(cfg_[section][key].c_str());}private:voidltrim(std::string str) {if(str.empty())return;size_tlen 0;char* temp (char*)str.c_str();while(*temp isblank(*temp)) {len;temp;}if(len 0) str.erase(0, len);}voidrtrim(std::string str) {if(str.empty())return;size_tlen str.length();size_tpos len;while(pos 0) {if(not isblank(str[pos - 1])) {break;}--pos;}if(pos ! len) str.erase(pos);}voidtrim(std::string str) {ltrim(str);rtrim(str);}voidsetSectionKeyValue(std::string section, std::string key, std::string value) {if(cfg_.find(section) cfg_.end()) {std::unordered_mapstd::string, std::string kv_map;cfg_[section] kv_map;}if(key ! value !) cfg_[section][key] value;}boolparseLine(std::string line, std::string section, std::string key, std::string value) {staticstd::string cur_section ;std::string nodes[2] {#,;};//去掉注释的内容for(inti 0; i 2; i) {std::string::size_type pos line.find(nodes[i]);if(pos ! std::string::npos) line.erase(pos);}trim(line);if(line )returnfalse;if(line[0] [ line[line.size() - 1] ]) {section line.substr(1, line.size() - 2);trim(section);cur_section section;returnfalse;}if(cur_section )returnfalse;boolis_key true;for(size_ti 0; i line.size(); i) {if(line[i] ) {is_key false;continue;}if(is_key) {key line[i];}else{value line[i];}}section cur_section;trim(key);trim(value);returntrue;}private:std::unordered_mapstd::string, std::unordered_mapstd::string, std::string cfg_;};// ini格式配置文件的读取}// namespace ConfigConfig命名空间下实现了Ini配置读取类。Load函数用于加载配置文件内容GetStrValue函数和GetIntValue函数用于获取配置项值并支持设置默认值Dump函数用于遍历配置文件的内容。由于在解析过程中需要删除字符串中的前导和后导空白符因此我们还实现了trim函数用于删除前导和后导空白符。这里重点讲解一下Load函数的逻辑每次从配置文件中读取一行然后先去掉注释的内容接着再判断剩余的内容是一个section头配置还是section下的key-value配置再走不同的解析分支。4.demo示例以上面配置文件demo.cfg内容的读取为例示例代码如下。123456789101112#include iostream#include config.hppintmain(intargc,char*argv[]) {Config::Ini ini;ini.Load(./demo.cfg);ini.Dump([](conststd::string section,conststd::string key,conststd::string value) {std::cout section[ section ],key[ key ]-value[ value ] std::endl;});return0;}5.自动生成读取代码如果这次分享的内容到上面demo示例之后就进入尾声的话那么春哥就太过于标题党了。假设我们的程序有几十项配置内容如果每一项采用GetIntValue函数或者GetStrValue函数来读取那么编码工作量还是不小的并且也容易出错那么怎么做到提效呢其实提效方案并不难想到我们可以自动生成读取配置项的代码并生成具体业务配置读取类。下面我们举一个例子假设我们有一个配置文件mysvr.cfg它的内容如下。[server]ip 127.0.0.1port 8080[pool]conn_pool_size 100我们手动编写了业务配置读取类代码文件MySvrCfg.hpp它的内容如下。1234567891011121314151617181920212223242526#include string#include config.hppclassMysvrCfg {public:boolLoad(std::string file_name) {Config::Ini ini;if(not ini.Load(file_name)) {returnfalse;}ini.GetIntValue(pool,conn_pool_size, conn_pool_size_, 0);ini.GetIntValue(server,port, port_, 0);ini.GetStrValue(server,ip, ip_,);returntrue;}int64_t conn_pool_size() {returnconn_pool_size_; }int64_t port() {returnport_; }std::string ip() {returnip_; }public:int64_t conn_pool_size_;int64_t port_;std::string ip_;};我们可以发现上面的代码完全可以自动生成。「我们先读取配置的内容然后使用配置文件的内容作为元数据驱动生成这个MySvrCfg.hpp的内容」。自动生成业务配置读取类的脚手架工具代码文件configtool.cpp它的内容如下。1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495#include iostream#include regex#include string#include MysvrCfg.hppusingnamespacestd;intgenCfgReadFile(Config::Ini ini, string file_name) {string prefix ;for(size_ti 0; i file_name.size(); i) {if(file_name[i] .)break;if(prefix ) {prefix toupper(file_name[i]);}else{prefix file_name[i];}}string class_name prefix Cfg;string output_file_name prefix Cfg.hpp;ofstream out;out.open(output_file_name);if(not out.is_open()) {cout open output_file_name failed. endl;return-1;}string cfg_read_content;string class_func_content;string class_member_content;ini.Dump([cfg_read_content, class_func_content, class_member_content](conststring section,conststring key,conststring value) {regex integer_regex([-]?[0-9]);if(regex_match(value, integer_regex)) {// 整数cfg_read_content ini.GetIntValue( section , key , key _, 0);\n;class_func_content int64_t key () { return key _; }\n;class_member_content int64_t key _;\n;}else{cfg_read_content ini.GetStrValue( section , key , key _, );\n;class_func_content std::string key () { return key _; }\n;class_member_content std::string key _;\n;}});//string content R(#include string#include config.hppclass) class_name R( {public:boolLoad(std::string file_name) {Config::Ini ini;if(not ini.Load(file_name)) {returnfalse;}) cfg_read_content R(returntrue;}) class_func_content R(public:) class_member_content };;out content;return0;}intreadDemoCfg() {MysvrCfg cfg;cout usage: configtool cfg_file_name endl;cout read demo cfg mysvr.cfg endl;cfg.Load(./mysvr.cfg);cout ip cfg.ip() endl;cout port cfg.port() endl;cout conn_pool_size cfg.conn_pool_size() endl;return0;}intmain(intargc,char*argv[]) {if(argc 1) {returnreadDemoCfg();}if(argc ! 2) {cout usage: configtool mysvr.cfg endl;return-1;}Config::Ini ini;string file_name argv[1];if(not ini.Load(file_name)) {cout load file_name failed. endl;return-1;}returngenCfgReadFile(ini, file_name);}在configtool脚手架工具中「我们先使用Config::Ini类对象读取了配置文件的内容然后遍历配置文件的内容生成业务配置读取类中动态变化的代码内容最后使用模版生成最终的代码」。脚手架工具configtool的使用也非常简单直接把配置文件名作为命令行参数传入即可如果执行configtool时不携带任何参数则会使用生成的类MysvrCfg来读取上面的配置文件mysvr.cfg的内容。