1. 准备:安装Yaml库

概述:YAML是一个可读性高,用来表达数据序列的格式。YAML参考了其它多种语言,包括:C语言、Python、Perl,并从XML、电子邮件的数据格式中获得灵感。Clark Evans在2001年首次发表了这种语言。当前已经有数种编程语言或脚本语言支持这种语言。最新稳定版本为1.2,于2009年发布。YAML文件扩展名为.yaml或.yml。

1.1 安装步骤

gihub上下载官方提供的库 版本0.70 最新版
https://github.com/jbeder/yaml-cpp/releases/tag/yaml-cpp-0.7.0

tar -xzvf yaml-cpp-yaml-cpp-0.7.0.tar.gz
image.png
cd yaml-cpp-yaml-cpp-0.7.0<br />mkdir build
cd build``<br />cmake..
make
sudo make install
安装完成

1.2 基本使用语法

具体详细语法介绍可以参考:https://blog.csdn.net/fengbingchun/article/details/88090609
编写矫正网站:https://www.bejson.com/validators/yaml_editor/
注意: 只能使用空格作为缩进字符,不能使用Tab。并且缩进格式也很严格。
以日志系统配置为例子:

  1. # (_表示空格符)
  2. # 散列表数据结构 冒号+空格
  3. # 清单数据结构(数组) 短横杠+空格
  4. logs:
  5. - name: root
  6. level: info
  7. formatter: "%d%T%m%n"
  8. appender:
  9. - type: FileLogAppender
  10. path: log.txt
  11. - type: StdOutLogAppender
  12. - name: system
  13. level: debug
  14. formatter: "%d%T%m%n"
  15. appender:
  16. - type: FileLogAppender
  17. path: log.txt
  18. - type: StdOutLogAppender

1.3 Yaml分层关系图

以1.2中log.yaml为例子
配置系统开发  (二) - 图2

2. 使用Yaml文件格式通过配置系统完成对某一子系统的配置

2.2 把读入的Yaml文件结构进行序列化

核心思想:由原来的多层级结构,序列化成一个字符串序列,形成一个KV键值对,存储于STL容器中。对于已经存在的配置信息,将读入的.yaml进行一个查找并且替换;如果不存在的配置信息考虑新创建一个ConfigVar对象用于存储,并且同步到已有配置库中Config:: std::map<std::string, ConfigVarBase::ptr>
配置系统开发  (二) - 图3

2.2.1 实现基本对基本数据类型的序列化操作

  • 核心序列化函数**config.cpp: static void ListAllMember()**(首先实现**Map**对象的序列化)

采用std::list容器是考虑增删比较快。如果多个字段重复的问题的话要考虑一下std::map容器。

  • 序列化子模块的时序逻辑如下: 配置系统开发  (二) - 图4```cpp static void ListAllMember(const std::string& prefix, const YAML::Node& node, std::list >&output) { //检查key值合法性 if(prefix.find_first_not_of(“abcdefghijklmnopqrstuvwxzy._0123456789”) != std::string::npos) {

    1. KIT_LOG_ERROR(KIT_LOG_ROOT()) << "Config invauld nmae:" << prefix << ":" << node;
    2. throw std::invalid_argument(prefix);

    }

    //将key : Node对象加入容器 output.push_back({prefix, node});

    //如果为Map类型 递归处理 if(node.IsMap()) {

    1. for(auto it = node.begin();it != node.end();++it)
    2. {
    3. ListAllMember(prefix.size() ? prefix + '.' + it->first.Scalar() : it->first.Scalar(), it->second, output);
    4. }

    }

}

void Config::LoadFromYaml(const YAML::Node &root) { std::list > all_nodes;

  1. ListAllMember("", root, all_nodes);
  2. int index = 0;
  3. for(auto &x : all_nodes)
  4. {
  5. std::string key = x.first;
  6. //空字符串就跳过
  7. if(!key.size())
  8. continue;
  9. //过滤大写字母
  10. std::for_each(key.begin(), key.end(), [&](char &c){ if(c >= 'A' && c <= 'Z') c += 32;});
  11. //从ConfigVar 寻找是否有已经约定的配置项,如果有就进行一个修改
  12. //是一个多态接口
  13. ConfigVarBase::ptr v = LookUpBase(key);
  14. if(v)
  15. {
  16. if(x.second.IsScalar())
  17. {
  18. v->fromString(x.second.Scalar());
  19. }
  20. else
  21. {
  22. std::stringstream ss;
  23. ss << x.second;
  24. v->fromString(ss.str());
  25. }
  26. }
  27. }

}

  1. - `**ConfigVar**`**类中 基本数据类型转换的依赖函数:toString() fromString()**
  2. ```cpp
  3. //将任意输入内容全部转换为String类型
  4. std::string toString() override
  5. {
  6. try{
  7. return boost::lexical_cast<std::string>(m_val);
  8. }catch (std::exception &e){
  9. KIT_LOG_ERROR(KIT_LOG_ROOT()) << "ConfigVar::toString exception" << e.what() << " convert:" << typeid(m_val).name() << " to string";
  10. }
  11. return "";
  12. }
  13. //将传入的字符串转为 对应的 所需基本类型的值
  14. bool fromString(const std::string &val) override
  15. {
  16. try{
  17. m_val = boost::lexical_cast<T>(val);
  18. return true;
  19. }catch(std::exception &e){
  20. KIT_LOG_ERROR(KIT_LOG_ROOT()) << "ConfigVar::fromString exception " << e.what() << " convert: string to " << typeid(m_val).name();
  21. }
  22. return false;
  23. }

2.2.2 实现复杂类型的序列化

配置系统的原则:约定优于配置。即:一般使用代码写好配置(服务器的固定端口、IP地址等)不会去轻易的改变称为一种“约定”。但不可能永远不改动,并且改动的配置项实际来说应该是非常少的,几百个配置中需要改动的可能只是十几个。遵循这个原则可以减少配置量。

改造思想:在保留原来对基础数据类型解析的基础上,根据2.2.1toStringfromString两个转换函数的改造。由原本借boost::lexical_cat万能转换实现基本类型转换,改造为:通过仿函数+模板的形式,形成一种对各种复杂类型的泛化型序列化。

2.2.2.1 保留基本数据类型转换,抽象为一个仿函数:

  1. fromString: T operator()(const string&)
  2. toString: string operator()(const T&)
  3. //将boost::lexical_cast函数使用仿函数包装一下 保留基础数据类型的转换
  4. //type F ---> type T
  5. //基础类型的转换
  6. template<class F, class T>
  7. class LexicalCast
  8. {
  9. public:
  10. T operator()(const F& v)
  11. {
  12. return boost::lexical_cast<T>(v);
  13. }
  14. };
  15. //将特化模板进行一个 默认参数配置
  16. template<class T, class FromStr = LexicalCast<std::string, T>, class ToStr = LexicalCast<T, std::string> >
  17. class ConfigVar :public ConfigVarBase
  18. {
  19. ...
  20. //将任意输入内容全部转换为String类型
  21. std::string toString() override
  22. {
  23. ...
  24. return ToStr()(m_val);
  25. ...
  26. }
  27. //将传入的字符串转为 对应的 所需类型的值
  28. bool fromString(const std::string &val) override
  29. {
  30. ...
  31. setValue(FromStr()(val));
  32. ...
  33. }
  34. ...
  35. };

2.2.2.2 支持STL序列式容器(以std::vector<>为例,listdeque等同略)使用模板的偏特化机制:

  1. //vector模板偏特化 string---->vector<> 正向序列化
  2. template<class T>
  3. class LexicalCast<std::string, std::vector<T> >
  4. {
  5. public:
  6. typename std::vector<T> operator()(const std::string& val)
  7. {
  8. typename std::vector<T> mv;
  9. //利用到YAML库的Load() string----->YAML::Node
  10. YAML::Node node = YAML::Load(val);
  11. for(size_t i = 0;i < node.size();++i)
  12. {
  13. std::stringstream ss;
  14. ss << node[i];
  15. //递归调用解析
  16. mv.push_back(LexicalCast<std::string, T>()(ss.str()));
  17. }
  18. return mv;
  19. }
  20. };
  21. //vector模板偏特化 vector<>---->string 反向序列化
  22. template<class F>
  23. class LexicalCast<std::vector<F>, std::string>
  24. {
  25. public:
  26. std::string operator()(const std::vector<F>& mv)
  27. {
  28. YAML::Node node;
  29. for(auto&x : mv)
  30. {
  31. node.push_back(YAML::Load(LexicalCast<F, std::string>()(x)));
  32. }
  33. std::stringstream ss;
  34. ss << node;
  35. return ss.str();
  36. }
  37. };

2.2.2.3 支持STL关联式容器(以std::map为例, setunordered_map等同略)使用模板的偏特化机制:

  1. //map模板偏特化 string---->map<>
  2. template<class T1, class T2>
  3. class LexicalCast<std::string, std::map<T1, T2> >
  4. {
  5. public:
  6. typename std::map<T1, T2> operator()(const std::string& val)
  7. {
  8. typename std::map<T1, T2> mv;
  9. //利用到YAML库的Load() string----->YAML::Node
  10. YAML::Node node = YAML::Load(val);
  11. for(auto it = node.begin();it != node.end();++it)
  12. {
  13. std::stringstream ss1, ss2;
  14. ss1 << it->first;
  15. ss2 << it->second;
  16. //递归调用解析
  17. mv.insert({LexicalCast<std::string, T1>()(ss1.str()), LexicalCast<std::string, T2>()(ss2.str())});
  18. }
  19. return mv;
  20. }
  21. };
  22. //map模板偏特化 map<>---->string
  23. template<class F1, class F2>
  24. class LexicalCast<std::map<F1, F2>, std::string>
  25. {
  26. public:
  27. std::string operator()(const std::map<F1, F2>& mv)
  28. {
  29. YAML::Node node;
  30. for(auto&x : mv)
  31. {
  32. node[YAML::Load(LexicalCast<F1, std::string>()(x.first))] = YAML::Load(LexicalCast<F2, std::string>()(x.second));
  33. }
  34. std::stringstream ss;
  35. ss << node;
  36. return ss.str();
  37. }
  38. };

注意:上面还是用到了boost::lexical_cast这个函数只能做基本类型转换,不支持自定义类型struct

2.2.2.4 支持自定义类型使用模板的偏特化机制:

  1. class Person
  2. {
  3. public:
  4. std::string m_name;
  5. int m_age = 0;
  6. bool m_sex = 0;
  7. Person(){};
  8. Person(const std::string& name, int age, bool sex)
  9. :m_name(name), m_age(age), m_sex(sex){}
  10. public:
  11. std::string toString() const
  12. {
  13. std::stringstream ss;
  14. ss << "[Person name = " << m_name
  15. << ", age=" << m_age
  16. << ", sex=" << m_sex
  17. << "]";
  18. return ss.str();
  19. }
  20. };
  21. //自定义类型偏特化 string--------->Person 序列化
  22. template< >
  23. class LexicalCast<std::string, Person>
  24. {
  25. public:
  26. Person operator()(const std::string &val)
  27. {
  28. Person p;
  29. YAML::Node node = YAML::Load(val);
  30. p.m_name = node["name"].as<std::string>();
  31. p.m_age = node["age"].as<int>();
  32. p.m_sex = node["sex"].as<bool>();
  33. return p;
  34. }
  35. };
  36. //自定义类型偏特化 Person--------->string 反序列化
  37. template<>
  38. class LexicalCast<Person, std::string>
  39. {
  40. public:
  41. std::string operator()(const Person& p)
  42. {
  43. YAML::Node node;
  44. node["name"] = p.m_name;
  45. node["age"] = p.m_age;
  46. node["sex"] = p.m_sex;
  47. std::stringstream ss;
  48. ss << node;
  49. return ss.str();
  50. }
  51. };



C++知识点补充复习1:模板特化、偏特化

本质:在原有泛化的基础上,选择固定一部分参数的类型。全部固定称:特化、部分固定:偏特化。
注意:①类模板有全特化、偏特化;函数模板只有全特化,偏特化通过函数重载就能实现
②特化并不是一种重载机制,而是将一个模板实例化的过程

  • 和有默认模板参数的区别:

    1. 特化后如果有两个参数依然需要传入两个参数,若传入的参数符合某一个特化会触发调用特化/偏特化的类实例化。
    2. 有默认模板参数,则只用传入一个参数也能完成模板实例化。
  • 例子如下: ```cpp template class A { public: void test(T a, F b) {

    1. cout << "无特化:" << a + b << endl;

    } };

template<> class A { public: void test(int a, int b) { cout << “全特化:” << a + b << endl; } };

template class A { public: void test(float a, F b) { cout << “偏特化:” << a + b << endl; } };

int main() { A a1; A a2; A a3; A a4; a1.test(1, 2.3); a2.test(1, 2.3); a3.test(1, 2.3); a4.test(1, 2.3);

  1. return 0;

}

  1. - **运行结果如下:**
  2. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/25460685/1638111945110-a4b7f9dc-1d47-4255-be19-c1eb1d36002b.png#clientId=uee323d82-8bdc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=83&id=u5319a9bb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=83&originWidth=533&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5712&status=done&style=none&taskId=ub7475256-044d-42ca-a13a-f652e6b2e07&title=&width=533)
  3. <a name="ES9lN"></a>
  4. #### C++知识点补充复习2:仿函数/函数对象
  5. 概念:重载了**函数调用操作符**`operator()`的类,这种类的对象一般称函数对象,由于调用时行为酷似普通函数调用,也称仿函数。
  6. **谓词**概念:返回一个bool值的仿函数称谓词。
  7. 本质:函数对象/仿函数是一个类不是一个函数
  8. ```cpp
  9. class A
  10. {
  11. ...
  12. public:
  13. XXX operator()(参数1,参数2,...)
  14. {
  15. }
  16. ...
  17. };
  • 和普通函数的区别:

① 函数对象拥有自己状态,所谓的”状态”就是类内部包含的成员变量、成员函数;
普通函数不存在这种状态,需要借助全局变量或静态变量实现一些相同的效果。
②函数对象可以作为参数来传递;普通函数要想传递,需要借助函数指针。

  • **#include <function>**头文件中包含很多内置的仿函数:
  1. 算术仿函数:

plus<T> 加法仿函数 minus<T> 减法仿函数 modulus<T> 取模仿函数
multiplies<T> 乘法仿函数 divides<T> 除法仿函数 negate<T> 取反仿函数

  1. 关系仿函数:

equal_to<T>等于仿函数 not_equal_to<T>不等于仿函数
greater<T>大于仿函数 less<T>小于仿函数

  1. 逻辑仿函数:

logical_and<T>逻辑与仿函数 logical_or<T>逻辑或仿函数
logical_not<T>逻辑非仿函数


2.2.3 思考:当读入key值一样,但是所表示数据甚至说数据类型不同如何处理?

  • 问题代码:

image.png
这里对key值是否存在的判断存在一些歧义:当配置系统中已经有一个同名key,但是数据类型T不相同的话,通过智能指针转换这个函数依旧会返回一个nullptr无法正确表示是否是真的不存在key值。

解决方案:key值已经存在,但所表示的类型不一致,直接报错提示;key值真的不存在,返回nullptr
image.png

3. 配置系统的事件机制

功能:当一个配置项发生修改时能够触发对应的函数调用。
原理:为ConfigVar类设置一个成员std::map<key, 回调函数>。通过该容器存储对应的回调函数,并使用std::function<void (const T& old_value, const T& new_value)>来封装对应回调函数。

小技巧:使用map来装回调函数的理由

  • 因为function<>内部不存在比较函数的重载(例如opertor==)导致无法使用比较的方式,得出哪一个function对象是我们需要操作的,需要手动使用一个map传入key值来指示对应的function对象。自然,key是不能够重复的。vector等序列式容器无法使用来装function对象

  • 改动如下:

image.png
image.png

  • **ConfigVar::setValue**函数改动如下:

    1. void setValue(const T& val)
    2. {
    3. //要知道原来的值是否和新值一样
    4. if(val == m_val)
    5. return;
    6. for(auto &x : m_cbs)
    7. x.second(m_val, val); //如果不一样 就要调用回调函数执行相关操作
    8. m_val = val;
    9. }

C++知识点补充复习:function类模板:函数包装器

概念:function<T>用于存储一个可调用对象(常常指的就是能够执行函数调用操作的对象),该可调用对象的类型应该与T相同。包括:普通函数、成员函数、函数指针、lambda表达式、函数对象、绑定表达式等。

注意:function中没有数据成员,只有成员函数,且只有基本的类成员函数:构造、析构、operator=、operator( )、operator bool。因此,无法与序列式容器配合使用function,模板中没有对应的比较方法的重载,无法快速索引。

  • 例子如下: ```cpp

    include

    include

    include

    include

using namespace std;

class A { public:

  1. int class_add(int a, int b)
  2. {
  3. return a + b;
  4. }
  5. int operator()(const int a, const int b)
  6. {
  7. return a + b;
  8. }
  9. int num;

};

int m_add(int a, int b) { return a + b; }

int main() {

  1. //存储普通函数
  2. function<int(int, int)> f1 = m_add;
  3. //存储函数指针
  4. int (*t)(int, int) = m_add;
  5. function<int(int, int)> f2 = t;
  6. //存储函数对象
  7. function<int(int, int)> f3 = A();
  8. //存储lambda表达式
  9. function<int(int, int)> f4 = [](int a, int b)->int{return a + b;};
  10. //存储类成员函数的函数指针
  11. function<int(A&, int, int)> f5 = &A::class_add;
  12. //存储类成员公有数据成员
  13. function<int(const A&)> f6 = &A::num;
  14. cout << f1(1,1) << endl;
  15. cout << f1(2,2) << endl;
  16. cout << f1(3,3) << endl;
  17. cout << f1(4,4) << endl;
  18. cout << f1(5,5) << endl;
  19. cout << f1(6,6) << endl;
  20. return 0;

} ```

  • 运行结果:

image.png

  • 和C语言函数指针的异同?

相同点:提供一个函数的地址,以供存储和使用

不同点:function<>就是管理函数指针的一个模板类,在一些场合下lambda表示式无法解释为一个函数指针,function<>也能将其纳入管理。其出现是为了更好的处理其他C++11新特性。