准备:配置boost库

概述:boost库是一个优秀且可以移植的开源C++库,是C++标准委员会库工作成员发起,是对STL的延伸和扩充,设计理念和STL比较接近,利用泛型编程达到利用率最大化,有些内容经常成为下一代C++标准库的内容

1. 下载压缩包

https://www.boost.org/ 进入boost官网下载压缩包,下载的是boost_1_77_0.tar.gz v1.77最新版本

tar -xzvf [boost_1_77_0.tar.gz](https://boostorg.jfrog.io/artifactory/main/release/1.77.0/source/boost_1_77_0.tar.gz) 解压后得到文件夹:
image.png

2. 执行脚本文件

cd boost_1_77_0 进入文件夹后执行脚本sudo ./bootstrap.sh
image.png

3. 执行安装

结束后继续执行sudo ./b2 install 完成安装

· 类关系

配置系统开发  (一) - 图3

1. ConfigVarBase 类封装 (接口类)

目的:提供虚接口,实现对单个配置项的维护管理

1.1 成员变量

  1. class ConfigVarBase
  2. {
  3. ...
  4. ...
  5. protected:
  6. /// 配置项名称
  7. std::string m_name;
  8. /// 配置项描述
  9. std::string m_description;
  10. };

1.2 接口

1.2.1 构造函数

  1. /**
  2. * @brief 配置项虚基类构造函数
  3. * @param[in] name 配置项名称
  4. * @param[in] description 配置项描述
  5. */
  6. ConfigVarBase(const std::string & name, const std::string &description = "")
  7. :m_name(name),
  8. m_description(description)
  9. {
  10. //把配置项的命名限制在小写范围内 输入的大写全部转为小写
  11. std::for_each(m_name.begin(), m_name.end(), [&](char &c){ if(c >= 'A' && c <= 'Z') c += 32;});
  12. }

· 对输入的Yaml文件key值(配置项名称),限定在小写范围内

  • 做法一:算法transform(sourceBeg, sourceEnd, destBeg, op)——>针对源区间
    [sourceBeg, sourceEnd)中的每一个元素调用:op(elem) 并将返回结果拷贝写到以destBeg起始的目标区间内。std::transform(m_name.begin(), m_name.end(), m_name.begin(), tolower);

  • 做法二:算法for_each(_InIt _First, _InIt _Last, _Fn _Func)——->针对源区间
    [InIt _First, _InIt _Last)中的每一个元素调用:Fn _Func(elem) 采用引用传递,且函数一般没有返回值,通过引用的方式改变传入参数的值。std::for_each(m_name.begin(), m_name.end(), [&](char &c){ if(c >= 'A' && c <= 'Z') c += 32;}

  • 二者区别:transfrom的效率不高,通过拷贝函数返回值的方式对目的区间进行赋值,但操作函数不采用引用传递,比较灵活;for_each的效率较高,因为操作函数通过引用传递的方式改变操作元素的值,不存在拷贝,但是比较死板。

1.2.2 虚接口

  1. /**
  2. * @brief 将任意输入内容转换为string类型
  3. * @return std::string
  4. */
  5. virtual std::string toString() = 0;
  6. //用于解析 初始化
  7. /**
  8. * @brief 从string类型转换为其他所需类型
  9. * @param[in] val 传入的字符串数据
  10. * @return true 转换成功
  11. * @return false 转换失败
  12. */
  13. virtual bool fromString(const std::string &val) = 0;
  14. /**
  15. * @brief 获取配置项类型名称
  16. * @return std::string
  17. */
  18. virtual std::string getTypeName() const = 0;

1.2.3 常用接口

  1. /**
  2. * @brief 获取配置项名称
  3. * @return const std::string&
  4. */
  5. const std::string & getName() const {return m_name;}
  6. /**
  7. * @brief 获取配置项描述
  8. * @return const std::string&
  9. */
  10. const std::string& getDescription() const {return m_description;}

2. LexicalCast 类封装

目的:完成对基本类型、复合类型、自定义类型与string字符串类型之间的相互转换。只需要将待转化的类型进行一个模板偏特化就能实现,无需重复实现相同形式的代码,尽量复用现有代码。

详情见:配置系统开发(二)篇章

  • 技术点:利用模板偏特化、仿函数,尽可能的去简化这个模板类的使用。通过制定待定参数FT数据类型完成模板的偏特化。 ```cpp /**
    • @brief 类型转换,只完成基础类型转换 type F —-> type T
    • @tparam F 待转换类型
    • @tparam T 转换后类型 */ template class LexicalCast { public: T operator()(const F& v) {
      1. return boost::lexical_cast<T>(v);
      }

};

  1. <a name="umO8B"></a>
  2. # 3. ConfigVar 类封装
  3. 目的:实现`ConfigVarBase`类的接口,并且置为模板类,方便自由的传入参数类型,而后调用 2. 中封装中好的万能转换去完成各种类型的序列化和反序列化,能使得配置项的存储和落盘较为便捷
  4. <a name="hVb94"></a>
  5. ## 3.1 成员变量
  6. ```cpp
  7. /**
  8. * @brief 配置项类
  9. * @tparam T 维护配置项的数据类型
  10. * @tparam FromStr 从字符串转为T类型采用的模板类
  11. * @tparam ToStr 从T类型转为字符串类型采用的模板类
  12. */
  13. template<class T, class FromStr = LexicalCast<std::string, T>,
  14. class ToStr = LexicalCast<T, std::string> >
  15. class ConfigVar :public ConfigVarBase
  16. {
  17. ...
  18. ...
  19. private:
  20. /// 配置项数据
  21. T m_val;
  22. /// 配置项所绑定的回调函数容器
  23. std::map<uint64_t, on_change_cb> m_cbs;
  24. /// 读写锁
  25. MutexType m_mutex;
  26. };

3.2 接口

3.2.1 构造函数

  1. /**
  2. * @brief 配置项类构造函数
  3. * @param[in] name 配置项名称,作为key值
  4. * @param[in] default_value 配置项的默认值
  5. * @param[in] description 配置项的描述
  6. */
  7. ConfigVar(const std::string &name, const T& default_value,
  8. const std::string description = "")
  9. :ConfigVarBase(name, description)
  10. ,m_val(default_value)
  11. {
  12. }

3.2.2 重写接口

3.2.2.1 toString()

功能:将配置项数据内容转换为string类型。通过ToStr()接口自动的去匹配符合的类模板,再调用目标类模板中的函数对象完成转换操作

  1. /**
  2. * @brief 将配置项数据内容转换为string类型
  3. * @return std::string
  4. */
  5. std::string toString() override
  6. {
  7. try{
  8. //return boost::lexical_cast<std::string>(m_val);
  9. //加读锁
  10. MutexType::ReadLock lock(m_mutex);
  11. //调用函数对象
  12. return ToStr()(m_val);
  13. }catch (std::exception &e){
  14. KIT_LOG_ERROR(KIT_LOG_ROOT()) << "ConfigVar::toString exception" << e.what() << " convert:" << std::string(typeid(m_val).name()) << " to string";
  15. }
  16. return "";
  17. }

3.2.2.2 FromString()

功能:将配置项从string类型转换为其他所需类型。通过FromStr()接口自动的去匹配符合的类模板,再调用目标类模板中的函数对象完成转换操作,将转换好的值设置到配置项中。

  1. /**
  2. * @brief 将配置项从string类型转换为其他所需类型
  3. * @param[in] val 传入的字符串数据
  4. * @return true 转换成功
  5. * @return false 转换失败
  6. */
  7. bool fromString(const std::string &v) override
  8. {
  9. try{
  10. setValue(FromStr()(v));
  11. }catch(std::exception &e){
  12. KIT_LOG_ERROR(KIT_LOG_ROOT()) << "ConfigVar::fromString exception " << e.what() << " convert: string to " << std::string(typeid(m_val).name());
  13. }
  14. return false;
  15. }

3.2.2.3 getTypeName()

功能:获取配置项类型名称

  1. /**
  2. * @brief 获取配置项类型名称
  3. * @return std::string
  4. */
  5. std::string getTypeName() const override { return typeid(T).name();}

3.2.3 setValue()/getValue()(核心)

功能:设置/获取配置项的数据。setValue()函数在发生数据更新时,会负责触发配置项上绑定的回调函数。这种机制的应用场景比较重要:游戏数值更新触发对应事件、配置项热更新等。

  1. /**
  2. * @brief 获取配置项,传入什么类型就返回什么类型
  3. * @return const T
  4. */
  5. const T getValue()
  6. {
  7. MutexType::ReadLock lock(m_mutex);
  8. return m_val;
  9. }
  10. /**
  11. * @brief 给配置项设置具体的数值,从.yaml读到信息后同步到内存中来
  12. * @param val
  13. */
  14. void setValue(const T& val)
  15. {
  16. {
  17. //加读锁
  18. MutexType::ReadLock lock(m_mutex);
  19. //原来的值是否和新值一样 就不用设置
  20. if(val == m_val)
  21. return;
  22. //一个配置项可设置多个回调函数进行触发
  23. for(auto &x : m_cbs)
  24. x.second(m_val, val); //调用回调函数执行相关操作
  25. }//出作用域 读锁解锁
  26. //加写锁
  27. MutexType::WriteLock lock(m_mutex);
  28. m_val = val;
  29. }

3.2.4 添加/删除/获取/清除配置项上的回调函数

  1. /**
  2. * @brief 为配置项绑定一个回调函数
  3. * @param[in] cb 指定的回调函数
  4. * @return uint64_t 返回一个唯一标识回调函数的key值
  5. */
  6. uint64_t addListener(on_change_cb cb)
  7. {
  8. //内部自己生成一个key(id)来唯一标识回调函数 key不能重复
  9. static uint64_t cb_fun_id = 0;
  10. //加写锁
  11. MutexType::WriteLock lock(m_mutex);
  12. ++cb_fun_id;
  13. m_cbs[cb_fun_id] = cb;
  14. return cb_fun_id;
  15. }
  16. /**
  17. * @brief 删除配置项上的某一回调函数
  18. * @param[in] key 唯一标识回调函数的key值
  19. */
  20. void delListener(uint64_t key)
  21. {
  22. MutexType::WriteLock lock(m_mutex);
  23. m_cbs.erase(key);
  24. }
  25. /**
  26. * @brief 获取配置项上的某一回调函数
  27. * @param[in] key 唯一标识回调函数的key值
  28. * @return on_change_cb
  29. */
  30. on_change_cb getListener(uint64_t key)
  31. {
  32. MutexType::ReadLock lock(m_mutex);
  33. auto it = m_cbs.find(key);
  34. return it == m_cbs.end() ? nullptr : it->second;
  35. }
  36. /**
  37. * @brief 清除配置项上的所有回调函数
  38. */
  39. void clearListener()
  40. {
  41. MutexType::ReadLock lock(m_mutex);
  42. m_cbs.clear();
  43. }

4. Config 类封装

目的:对所有的配置项进行统一管理,更加快捷的创建、访问配置项。

4.1 成员变量

这样写的原因是程序开始运行进入main()函数之前,要保证容器和锁已经被初始化好,否则有可能出现有的文件中已经发生调用,但是这些资源还没初始化的情况。

  1. /**
  2. * @brief ConfigVar的管理类
  3. * @details 提供便捷的方法创建/访问ConfigVar
  4. */
  5. class Config
  6. {
  7. public:
  8. //将配置项容器重命名 方便使用
  9. typedef std::map<std::string, ConfigVarBase::ptr> ConfigVarMap;
  10. typedef RWMutex MutexType;
  11. ...
  12. ...
  13. private:
  14. /**
  15. * @brief 获取配置项容器,由于初始化顺序问题写成static函数形式
  16. * @return ConfigVarMap&
  17. */
  18. static ConfigVarMap& GetDatas()
  19. {
  20. static ConfigVarMap m_datas;
  21. return m_datas;
  22. }
  23. /**
  24. * @brief 获取读写锁
  25. * @return MutexType&
  26. */
  27. static MutexType& GetMutex()
  28. {
  29. static MutexType m_mutex;
  30. return m_mutex;
  31. }
  32. };

4.2 接口

4.2.1 LookUp()(核心)

功能:查询配置项

  1. /**
  2. * @brief 查询配置项,如果没有就会创建
  3. * @tparam T 配置项的参数类型
  4. * @param[in] name 配置项名称
  5. * @param[in] default_value 配置项默认值
  6. * @param[in] description 配置项描述
  7. * @return ConfigVar<T>::ptr
  8. */
  9. template<class T>
  10. static typename ConfigVar<T>::ptr LookUp(const std::string &name, const T& default_value,
  11. const std::string &description = "")
  12. {
  13. //加写锁
  14. MutexType::WriteLock lock(GetMutex());
  15. //判断查询的配置项是否存在
  16. auto it = GetDatas().find(name);
  17. if(it != GetDatas().end()) //配置项存在
  18. {
  19. auto temp = std::dynamic_pointer_cast<ConfigVar<T> >(it->second);
  20. if(temp)
  21. {
  22. return temp;
  23. }
  24. else
  25. {
  26. KIT_LOG_ERROR(KIT_LOG_ROOT()) << "LookUp name:" << name << " exists but not =" << typeid(T).name() << " real-type=" << it->second->getTypeName();
  27. return nullptr;
  28. }
  29. }
  30. /*配置项不存在就创建*/
  31. //排除一些非法的配置项名称
  32. if(name.find_first_not_of("abcdefghijklmnopqrstuvwxyz_.0123456789") != std::string::npos)
  33. {
  34. KIT_LOG_ERROR(KIT_LOG_ROOT()) << "LookUp name invalid " << name;
  35. //抛出无效参数异常
  36. throw std::invalid_argument(name);
  37. }
  38. typename ConfigVar<T>::ptr v(new ConfigVar<T>(name, default_value, description));
  39. GetDatas()[name] = v;
  40. return v;
  41. }
  42. /**
  43. * @brief 查询配置项,如果没有返回nullptr
  44. * @tparam T 配置项的参数类型
  45. * @param[in] name 配置项名称
  46. * @return ConfigVar<T>::ptr
  47. */
  48. template<class T>
  49. static typename ConfigVar<T>::ptr LookUp(const std::string &name)
  50. {
  51. //加读锁
  52. MutexType::ReadLock lock(GetMutex());
  53. auto it = GetDatas().find(name);
  54. if(it == GetDatas().end())
  55. {
  56. return nullptr;
  57. }
  58. //当转化对象为智能指针时使用dynamic_pointer_cast
  59. return std::dynamic_pointer_cast<ConfigVar<T> >(it->second);
  60. }

4.2.2 LoadFromYaml()(核心)

功能:从yaml结点中加载配置项数据。一般在调用YAML库的接口YAML::LoadFile(文件路径)后从文件中读取数据构造成一个yaml结点,再调用该接口从原始yaml结点中拆解出对应的配置项,依次对需要的配置进行一个设置。

  1. /**
  2. * @brief 从yaml结点中加载配置项数据
  3. * @param[in] node 带有数据的yaml结点
  4. */
  5. static void LoadFromYaml(const YAML::Node & node);
  6. void Config::LoadFromYaml(const YAML::Node &root)
  7. {
  8. std::list<std::pair<std::string, YAML::Node> > all_nodes;
  9. ListAllMember("", root, all_nodes);
  10. for(auto &x : all_nodes)
  11. {
  12. std::string key = x.first;
  13. //空字符串就跳过
  14. if(!key.size())
  15. continue;
  16. //将配置项名称中的大写字母全部转为小写字母
  17. std::for_each(key.begin(), key.end(), [&](char &c){ if(c >= 'A' && c <= 'Z') c += 32;});
  18. //寻找是否有已经约定的配置项,如果有就进行修改
  19. ConfigVarBase::ptr v = LookUpBase(key);
  20. if(v)
  21. {
  22. //如果当前yaml结点是标量型数据直接进行转化
  23. if(x.second.IsScalar())
  24. {
  25. v->fromString(x.second.Scalar());
  26. }
  27. else //如果当前yaml结点是复合数据需要以流的方式传入参数
  28. {
  29. std::stringstream ss;
  30. ss << x.second;
  31. v->fromString(ss.str());
  32. }
  33. }
  34. }
  35. }

· 辅助函数:ListAllMember()

功能:将yaml结点进行序列化,把每一个配置项扁平化

  1. /**
  2. * @brief 将yaml结点数据序列化
  3. * @param[in] prefix 格式控制+key值组合
  4. * @param[in] node 带有数据的yaml结点
  5. * @param[out] output 存储序列化结果的容器
  6. */
  7. static void ListAllMember(const std::string& prefix, const YAML::Node& node, std::list<std::pair<std::string, YAML::Node> >&output)
  8. {
  9. //检查key值的合法性
  10. if(prefix.find_first_not_of("abcdefghijklmnopqrstuvwxzy._0123456789") != std::string::npos)
  11. {
  12. KIT_LOG_ERROR(KIT_LOG_ROOT()) << "Config invauld nmae:" << prefix << ":" << node;
  13. throw std::invalid_argument(prefix);
  14. }
  15. //将 key:Node 存储到容器中
  16. output.push_back({prefix, node});
  17. //如果为Map类型 递归处理
  18. if(node.IsMap())
  19. {
  20. for(auto it = node.begin();it != node.end();++it)
  21. {
  22. //进行递归
  23. ListAllMember(prefix.size() ? prefix + '.' + it->first.Scalar() : it->first.Scalar(), it->second, output);
  24. }
  25. }
  26. }

4.2.3 LookUpBase()

功能:返回配置项的基类指针

  1. /**
  2. * @brief 返回配置项的基类指针
  3. * @param[in] name 配置项名称
  4. * @return ConfigVarBase::ptr
  5. */
  6. static ConfigVarBase::ptr LookUpBase(const std::string& name);
  7. ConfigVarBase::ptr Config::LookUpBase(const std::string& name)
  8. {
  9. //加读锁
  10. MutexType::ReadLock lock(GetMutex());
  11. auto it = GetDatas().find(name);
  12. return it == GetDatas().end() ? nullptr : it->second;
  13. }

4.2.4 Visit()

功能:轮询对所有配置项进行某个操作

  1. /**
  2. * @brief 轮询对所有配置项进行某个操作
  3. * @param[in] cb 指定一个函数去操作配置项
  4. */
  5. static void Visit(std::function<void(ConfigVarBase::ptr)> cb);
  6. void Config::Visit(std::function<void(ConfigVarBase::ptr)> cb)
  7. {
  8. MutexType::ReadLock lock(GetMutex());
  9. ConfigVarMap& m = GetDatas();
  10. for(auto &x : m)
  11. {
  12. cb(x.second);
  13. }
  14. }

boost库知识:万能转换器 boost::lexical_cast<>()

功能统一接口实现不同类型间的相互转换,即:万能转换

  • 使用样例:

    1. int a;
    2. //std::string s = "123.12"; //如果使用该字符串转换,a无法成功转换
    3. std::string s = "123";
    4. a = boost::lexical_cast<int>(s);
    5. std::cout << a << std::endl;
    6. std::string b = boost::lexical_cast<std::string>(a);
    7. std::cout << b << std::endl;
    8. double c = boost::lexical_cast<double>(s); //传入"123.12"能够转换成功
    9. std::cout << c << std::endl;
  • 和传统库函数的区别,C语言库的atoi、itoa 以及C++库的stringstream:

  1. C语言库的atoiitoa都是单向转换,没有双向转换,不同类型间转换需要依赖不同的接口函数
  2. 仅仅支持基本数据类型的转换,int、double
  3. C++中stringstream可用性强但是可读性差,使用门槛高,对于简单转换,太重。

注意:如果对转换的精度有要求应该使用stringstream。数值之间转换最好使用boost::numberic

C\C++知识点补充复习:dynamic_pointer_cast<>()

都是实现基类和派生类之间安全转换。dynamic_pointer_cast用于转换对象为智能指针的时候;dynamic_cast用于转换对象为普通指针或引用的时候。

C\C++知识点补充复习:transform()算法

功能:针对源区间**[**sourceBeg, sourceEnd**)**中的每一个元素调用操作:op(elem) 并将返回结果拷贝写到目标区间内。

  1. transform(sourceBeg, sourceEnd, destBeg, op)
  2. transform(sourceBeg, sourceEnd, destBeg, destEnd, op)

C\C++知识点补充复习:find_first_not_of()算法

功能正向查找在原字符串中第一个与指定字符串(或字符)中的任一字符都不匹配的字符,返回它的位置。若查找失败,则返回string::npos。(npos定义为保证大于任何有效下标的值。)

使用地方:判断字符串name中是否包含规定之外字符,如果包含就要throw出异常

  1. if(name.find_first_not_of("abcdefghijklmnopqrstuvwxyz_.0123456789") != std::string::npos)
  2. {
  3. KIT_LOG_ERROR(KIT_LOG_ROOT()) << "LookUp name invalid " << name;
  4. throw std::invalid_argument(name);
  5. }

手写算法替代:

  1. //判断传入的name是否合法 只能包含"abcdefghijklmnopqrstuvwxyz_.0123456789"这些字符
  2. bool isRight(const std::string &str)
  3. {
  4. for(auto &x : str)
  5. {
  6. if((x < 'a' || x > 'z' )&& !isdigit(x) && x != '.' && x != '_')
  7. return false;
  8. }
  9. return true;
  10. }