C++中的config设计

配置文件读写类,它要有以下这些方法:

1. 支持读入一个指定配置文件的能力

2. 支持随时加入一个配置项的能力

3. 足够强大,能够写入各种数据结构的配置信息

C++ 里,我们要存储这样的数据就使用 std::map 即可。

也就是说,我们的 Config 类中,需要有一个最基本最基本的存储配置文件键值对信息的 std::map 成员,这个成员用来将配置文件中的每个 key 值和其对应的 value 值记录下来。

那么另外一个问题也就来了,我们的 std::map 究竟应该是什么类型的呢?

哈哈,这个问题其实非常简单,因为我们的键值对信息都是要读出写入到文件的,那么 std::map 不论是 key 值还是 value 值都将会是字符串类型,即 C++ STL 的 std::string (Config 类不支持中文编码)类即可。

那么有人就会问了,如果 value 值只是一个简简单单的 std::string 类的话,我想要存储一个非常复杂的数据结构怎么办,比如一个 phone key 值,对应了一个电话号码列表呢?

这个问题其实也非常简单,这里的 std::map 成员只是 Config 类中的最基本最基本存储到文件里的字符串键值对记录,而 Config 为了支持用户存储多种复杂的 value 值,还提供了模板支持。因此,这里只需要你提供的 value 值的结构可以被转化为 std::string 类型,就可以使用 Config 类来存储你的数据结构了。

因此,让我们看看 Config 类的代码:

std::string m_Delimiter;  //!< separator between key and value
std::string m_Comment;    //!< separator between value and comments
std::map<std::string, std::string> m_Contents;  //!< extracted keys and values  
  • 1
  • 2
  • 3

这三个内部的属性, m_Delimiter 是我们之前提到的 key 值和 value 值的分隔符 = 的设置,m_Comment 是我们之前提到的注释内容开头 # 字符的设置,m_Contents 就是我们上面讨论的 std::map 对象,并且以 key 值和 value 值均为 std::string 类型存储。

此外,我们在 Config 类中看到的那么多的模板函数,其归根结底想要实现的,就是支持用户自定义的 value 数据结构的读取和写入:

//!<Search for key and read value or optional default value, call as read<T>
template<class T> T Read(const std::string& in_key) const;
// Modify keys and values
template<class T> void Add(const std::string& in_key, const T& in_value);
  • 1
  • 2
  • 3
  • 4

这里截取了两个重要的函数,一个用来读取 key 值对应的 value 值,一个用来添加一个键值对。可以看到,这里的 key 值永远都是一个 std::string 类型的对象,而相应的 value 值则是模板定义的类型,支持用户自定义传入任何的可以转成 std::string 类型的数据结构。

三、Config 设计之:暴露方法

接下来让我们想想这样一个问题,在我们看到了配置文件的内容之后,并且将其抽象成了 std::map 的数据结构,之后我们需要做的,就是给类的调用者暴露方法的方法即可。

那么应该有哪些方法呢:

1. 一个可以跟某个具体的配置文件绑定起来的构造函数

2. 获取指定 key 值的 value 值

3. 加入一对键值对

4. 修改指定 key 值的 value 值

5. 删除一对键值对

暂时就想到了这些比较重要的,那么 Config 类中提供了这些方法了吗?

哈哈,提供了,让我们一个一个来看:

1. 一个可以跟某个具体的配置文件绑定起来的构造函数

Config::Config(string filename, string delimiter, string comment)
    : m_Delimiter(delimiter), m_Comment(comment)
{
    // Construct a Config, getting keys and values from given file
    std::ifstream in(filename.c_str());
    if (!in) throw File_not_found(filename);
    in >> (*this);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

作者使用 std::ifstream 打开了一个本地文件,注意,调用这个方法之前必须保证该文件存在。我们要注意到作者调用了 in >> (*this),调用了本类的 operator>> 重载函数,用来读取文件内容(此函数过于冗长,可以自行查看源码)并将其存储到 std::map

//!<Search for key and read value or optional default value, call as read<T>
template<class T> T Read(const std::string& in_key) const;
template<class T> T Read(const std::string& in_key, const T& in_value) const;
template<class T> bool ReadInto(T& out_var, const std::string& in_key) const;
  • 1
  • 2
  • 3
  • 4

这三个都是模板函数,主要是用来获取用户自定义数据结构的 value 值。需要注意的是,这三个函数的用法,第一个是返回 value 值;第二个是可以将 value 值在参数中返回;第三个直接将 value 值写入到传入的 var 对象中。

3. 加入一对键值对 
4. 修改指定 key 值的 value 值 
作者直接使用了一个函数即完成了第 3 点和第 4 点的工作:

template<class T>
void Config::Add(const std::string& in_key, const T& value)
{
    // Add a key with given value
    std::string v = T_as_string(value);
    std::string key = in_key;
    Trim(key);
    Trim(v);
    m_Contents[key] = v;
    return;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这里使用了 C++ 的 std::map 的特性,如果 key 值在 std::map 中存在,则更新 value 值,否则就新增一对键值对。需要注意的是,这里调用了这行代码:

std::string v = T_as_string(value);
  • 1

其中 T_as_string 函数将用户传入的自定义模板类转化为 std::string 类型进行存储,而该方法的实现如下:

/* static */
template<class T>
std::string Config::T_as_string(const T& t)
{
    // Convert from a T to a string
    // Type T must support << operator
    std::ostringstream ost;
    ost << t;
    return ost.str();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这个类直接调用了用户自定义模板类的 operator<< 重载操作符函数,也就是说,只要用户自定义数据结构自定义重载了 operator<< 操作符函数,就可以用 Config 类来进行 value 值的读写操作了。

5. 删除一对键值对

void Config::Remove(const string& key)
{
    // Remove key and its value
    m_Contents.erase(m_Contents.find(key));
    return;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

幸而有 C++ STL 强大的功能,删除一对键值对就是这么简单。

6. 另外的一些方法 
作者为了方便用户使用,还提供了诸如查询文件是否存在、键值是否存在、读入文件、设置获取键值分隔符、设置获取注释标识符等等方法。都是比较简单并且易用的,感兴趣的同学可以自行查看源码。

四、Config 的使用 Demo

这里,我自行编写了一个 Demo 来测试 Config 类的功能:

#include <iostream>
#include <cstdlib>
#include <string>
#include <fstream>
#include "Config.h"

int main()
{
    // 打开一个写文件流指向 config.ini 文件
    std::string strConfigFileName("config.ini");
    std::ofstream out(strConfigFileName);
    // 初始化写入注释
    out << "# test for config read and write\n";
    // 写入一对配置记录: name = wangying
    out << "name = wangying\n";
    out.close();

    // 初始化 Config 类
    Config config(strConfigFileName);

    // 读取键值
    std::string strKey = "name";
    std::string strValue;
    strValue = config.Read<std::string>(strKey);
    std::cout << "Read Key " << strKey << "‘s Value is "
         << strValue << std::endl;

    // 写入新键值对
    std::string strNewKey = "age";
    std::string strNewValue = "23";
    config.Add<std::string>(strNewKey, strNewValue);

    // 将 Config 类的修改写入文件
    out.open(strConfigFileName, std::ios::app);
    if (out.is_open()) {
        // 利用 Config 类的 << 重载运算符
        out << config;
        std::cout << "Write config content success!" << std::endl;
    }
    out.close();

    system("pause");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

幸而有强大的 Config 类,让我操作配置文件变成了一件这么简单的事情!

然而这里需要注意的是,我们在使用 Config 类进行了 Add() 操作之后,我们仅仅只是在 Config 类中操作了 std::map 类型的 m_Contens 对象内容而已,我们还需要将其写入到文件中去,因此这里我最后调用了写文件流进行写入操作,注意这行代码:

// 利用 Config 类的 << 重载运算符
out << config;
  • 1
  • 2

这里隐含调用了 Config 类的 operator<< 重载运算符:

std::ostream& operator<<(std::ostream& os, const Config& cf)
{
    // Save a Config to os
    for (Config::mapci p = cf.m_Contents.begin();
        p != cf.m_Contents.end();
        ++p)
    {
        os << p->first << " " << cf.m_Delimiter << " ";
        os << p->second << std::endl;
    }
    return os;
}

原文地址:https://www.cnblogs.com/klb561/p/9147626.html

时间: 2024-10-12 19:21:58

C++中的config设计的相关文章

在.net中读写config文件的各种方法(自定义config节点)

http://www.cnblogs.com/fish-li/archive/2011/12/18/2292037.html 阅读目录 开始 config文件 - 自定义配置节点 config文件 - Property config文件 - Element config文件 - CDATA config文件 - Collection config文件 - 读与写 读写 .net framework中已经定义的节点 xml配置文件 xml配置文件 - CDATA xml文件读写注意事项 配置参数的

.Net中Web.config 配置详解

花了点时间整理了一下ASP.NET Web.config配置文件的基本使用方法.很适合新手参看,由于Web.config在使用很灵活,可以自定义一些节点.所以这里只介绍一些比较常用的节点. <?xml version="1.0"?> <!--注意: 除了手动编辑此文件以外,您还可以使用 Web 管理工具来配置应用程序的设置.可以使用 Visual Studio 中的“网站”->“Asp.Net 配置”选项. 设置和注释的完整列表在 machine.config.

看懂此文,不再困惑于 JS 中的事件设计

看懂此文,不再困惑于 JS 中的事件设计 今天刚在关注的微信公众号看到的文章,关于JS事件的,写的很详细也很容易理解,相关的知识点都有总结到,看完就有种很舒畅的感觉,该串起来的知识点都串起来了.反正一字节:爽. 作者:aitangyong 链接:blog.csdn.net/aitangyong/article/details/43231111 抽空学习了下javascript和jquery的事件设计,收获颇大,总结此贴,和大家分享. (一)事件绑定的几种方式 javascript给DOM绑定事件

asp.net中web.config配置节点大全详解【转】

web.config 文件查找规则: (1)如果在当前页面所在目录下存在web.config文件,查看是否存在所要查找的结点名称,如果存在返回结果并停止查找. (2)如果当前页面所在目录下不存在web.config文件或者web.config文件中不存在该结点名,则查找它的上级目录,直到网站的根目录. (3)如果网站根目录下不存在web.config文件或者web.config文件中不存在该节点名则在%windir%"Microsoft.NET"Framework"v2.0.

设计模式中的六大设计原则之三,四

求二叉树的宽度和深度 给定一个二叉树,获取该二叉树的宽度和深度. 例如输入 a / \ b c / \ / \ d e f g 返回3. 详细描述: 接口说明 原型: int GetBiNodeInfo(BiNode &head, unsigned int *pulWidth, unsigned int *pulHeight) 输入参数: head 需要获取深度的二叉树头结点 输出参数(指针指向的内存区域保证有效): pulWidth 宽度 pulHeight 高度 返回值: 0 成功 1 失败

APP中添加标签设计

app设计在视频/图片/文字发布过程中添加标签设计总结 标签,主要是给与用户上传的内容添加标签,这类标签主要有一下几点作用: 1.便于找到相似标签好友,提高产品社交属性: 2.便于归类内容,便于用户和后台进行数据抓取: 3.便于运营相关活动,提升产品互动性: 标签主要是在视频或照片拍摄/编辑结束后,在发布页出现的功能,发布页面具有的通用功能包括:封面(针对视频而言),图片缩略图,标题,描述,地点,标签,@他人,隐私权限,分享/同步到 几项信息,其中按照产品定位的权重及应用本身的属性特质,几点内容

情感设计中与标牌设计相关

在情感设计中与标牌设计相关的,主要是材料的选择,自然的材料往往能迎合大众的情感需求;其次是颜色的选择,一般而言会因人的差异和偏好的不同而不同,但是仍然有一定的规律,有一些光谱之内的色彩易被众人所接受;再是标牌色彩与其图底色彩之间的差别,强烈差异的色彩搭配,是有益于标牌被关注和辨识的,而如果趋向相同则难以被识别和关注,但相对和谐.另外色彩的使用还应关注对野生动物的影响,一般尽量少用视觉反映强烈的红色.标牌的大小也会影响到人的自我意识和反映,如近距离的大标牌(指不可避免的近距离)会给人以压抑,降低个

企业信息化中的管理设计深度探讨研究

管理设计是针对某一类企业进行的,可以说是企业个性化应用的完美体现,但这绝对不是个性化的泛滥.须知任何的企业,其信息化管理是不以管理者的意志为转移的,它不同于经营决策的管理,它必须严格依照基本的管理原理来进行,比如计划方法.订单跟踪.成本核算.预算管理等等,都是严密的信息逻辑支撑的确定性管理方法,只不过由于企业所处行业的不同,所涉及业务有所差异而有所变化而已,这种变化远不足以改变ERP软件的通用性和普遍适用性,而仅仅是要求ERP这个百宝箱中有更多的宝贝而已. 当我们以一种蓦然回首的心态重新审视所从

架构的坑系列:重构过程中的过度设计

架构的坑系列:重构过程中的过度设计 软件架构   2016-06-03 08:47:02 发布 您的评价:       5.0   收藏     2收藏 这个系列是 坑 系列,会说一些在系统设计,系统架构上的 坑 ,这些都是我想到哪说到哪,有像这篇一样比较宏观的 坑 ,后面的文章也会有到具体技术细节的(比如某个函数,某个系统调用) 坑 ,总之,到处都是坑,这些坑有些是我经历过的,有些是听说的,你也可以留言说说你遇到的 坑 . 这一篇,我们从 重构 这个场景来看看系统架构的设计中 过度设计 这个坑