请查看我的ini文件解析器(可能还有一些通用的配置文件格式)。
所使用的数据结构是一节,见下文。这看起来是个好办法吗?
我在想那个名字。它将解析ini文件,但unix配置文件是可变的,因此调用类配置可能会过多地出售它。
我不支持中线评论。只有对一行的评论才会被完全忽略。
所有值都存储为字符串。我认为只有配置文件的用户才会知道在他或她的应用程序中是否应该使用一个值作为字符串或整数或其他任何东西,然后由用户来进行转换。
如有任何意见,将不胜感激。它的最初用途是用于Windows文件。将配置解析为各个部分。
config.hpp
#ifndef CONFIG_HPP_
#define CONFIG_HPP_
#include <string>
#include <unordered_map>
#include <list>
struct section
{
std::string name;
std::unordered_map<std::string, std::string> keyvalues;
};
class config
{
public:
config(const std::string& filename);
section* get_section(const std::string& sectionname);
std::list<section>& get_sections();
std::string get_value(const std::string& sectionname, const std::string& keyname);
private:
void parse(const std::string& filename);
std::list<section> sections;
};
#endif // CONFIG_HPP_配置实现- config.cpp
#include "config.hpp"
#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
#include <unordered_map>
#include <list>
// trim leading white-spaces
static std::string& ltrim(std::string& s) {
size_t startpos = s.find_first_not_of(" \t\r\n\v\f");
if (std::string::npos != startpos)
{
s = s.substr(startpos);
}
return s;
}
// trim trailing white-spaces
static std::string& rtrim(std::string& s) {
size_t endpos = s.find_last_not_of(" \t\r\n\v\f");
if (std::string::npos != endpos)
{
s = s.substr(0, endpos + 1);
}
return s;
}
config::config(const std::string& filename) {
parse(filename);
}
section* config::get_section(const std::string& sectionname) {
std::list<section>::iterator found = std::find_if(sections.begin(), sections.end(), [sectionname](const section& sect) {
return sect.name.compare(sectionname) == 0; });
return found != sections.end() ? &*found : NULL;
}
std::list<section>& config::get_sections() {
return sections;
}
std::string config::get_value(const std::string& sectionname, const std::string&keyname) {
section* sect = get_section(sectionname);
if (sect != NULL) {
std::unordered_map<std::string, std::string>::const_iterator it = sect->keyvalues.find(keyname);
if (it != sect->keyvalues.end())
return it->second;
}
return "";
}
void config::parse(const std::string& filename) {
section currentsection;
std::ifstream fstrm;
fstrm.open(filename);
if (!fstrm)
throw std::invalid_argument(filename + " could not be opened");
for (std::string line; std::getline(fstrm, line);)
{
// if a comment
if (!line.empty() && (line[0] == ';' || line[0] == '#')) {
// allow both ; and # comments at the start of a line
}
else if (line[0] == '[') {
/* A "[section]" line */
size_t end = line.find_first_of(']');
if (end != std::string::npos) {
// this is a new section so if we have a current section populated, add it to list
if (!currentsection.name.empty()) {
sections.push_back(currentsection); // copy
currentsection.name.clear(); // clear section for re-use
currentsection.keyvalues.clear();
}
currentsection.name = line.substr(1, end - 1);
}
else {
// section has no closing ] char
}
}
else if (!line.empty()) {
/* Not a comment, must be a name[=:]value pair */
size_t end = line.find_first_of("=:");
if (end != std::string::npos) {
std::string name = line.substr(0, end);
std::string value = line.substr(end + 1);
ltrim(rtrim(name));
ltrim(rtrim(value));
currentsection.keyvalues[name] = value;
}
else {
// no key value delimitter
}
}
} // for
// if we are out of loop we add last section
// this is a new section so if we have a current section populated, add it to list
if (!currentsection.name.empty()) {
sections.push_back(currentsection); // copy
currentsection.name = "";
currentsection.keyvalues.clear();
}
}做一个简单测试的一些代码
#include "config.hpp"
#include <iostream>
#include <fstream>
#include <string>
void generate_config(const std::string& filename) {
std::ofstream ostrm;
ostrm.open(filename);
if (ostrm) {
ostrm << "[protocol]\nversion = 6 \n\n[user]\nname = Bob Smith \nemail = bob@smith.com \nactive = true\n\npi = 3.14159";
}
}
int main() {
// generate test file
generate_config("test1.ini");
// retrieve some information from config file
config cfg("test1.ini");
section* usersection = cfg.get_section("user");
if (usersection != NULL) {
std::cout << "section name: " << usersection->name << std::endl;
std::cout << "email=" << cfg.get_value("user", "email") << '\n';
}
}发布于 2016-05-08 18:29:20
在我看来不错。以下是几点意见:
节* get_section(const::string& sectionname);std::list& get_sections();
不太确定返回数据的可变指针/引用是个好主意,因为更改内存中的section对底层文件表示没有任何影响。这可能会导致误解。最好通过返回const指针和const ref来保持数据的不可变性。这也将允许const对方法本身进行限定。
const section* get_section(const std::string& sectionname) const;
const std::list<section>& get_sections() const;对于这些过于冗长的模板实例化,请使用auto:
std::unordered_map::const_iterator it =sect->keyname.查找(Keyname);
与:
const auto it = sect->keyvalues.find(keyname);这也将使生活更容易,如果容器或其内容在未来的变化。您只需更新keyvalues声明,而不是在任何地方都使用它。
另外,关于使用C++的当前特性集的问题,您应该更喜欢nullptr而不是NULL。因此,NULL隐式地转换为整数并导致函数重载解析混乱,因此要养成使用nullptr的习惯。
std::list还相关吗?有些人说,您应该忘记链接列表的存在,在这样一个CPU缓存和数据局部性对代码性能起着如此重要作用的世界里。对于大多数情况,std::vector是一个很好的默认设置,所以我将从向量开始。要帮助您做出决定,请看一下:效率与算法,性能与数据结构。
发布于 2016-05-31 15:51:51
具有单参数的
为了避免编译器进行隐式单个arg转换,请考虑声明构造函数:
配置(const::string& filename);
作为:
显式配置(const::string& filename);
https://codereview.stackexchange.com/questions/127819
复制相似问题