如何设计易用的c++参数配置文件?

Yaml vs. json,还是自己尝试设计一个吧

Posted by YuCong on August 19, 2020

如何设计简单易用的c++参数配置文件?


Created 2020.08.19 by Cong Yu; Last modified: 2022.09.09-v1.0.2

Contact: windmillyucong@163.com

Copyleft! 2022 Cong Yu. Some rights reserved.


Config for c++

References

1. 设计

想要设计一套简单易用的config解析应该包含一下几个部分:

  1. 文件的读取与写出
  2. ConfigSet的实现
  3. Config的实现
1.1 config_io

其中文件的读写应该交给config_io.cc。需要实现一些文件IO的操作,

  • 包括ReadConfigs() 输入 std::string file_name, 输出 ConfigSet
  • WriteConfigs() 输入ConfigSet. 输出到文件。
1.2 ConfigSet

ConfigSet 表达某文件中的一组config,需要实现的方法有

  • bool Add(const std::string &name, const std::shared_ptr<Config> &config) 用于添加一个config到configset中
  • bool Remove(const std::string) 用于从configset中删掉某个config
  • std::shared_ptr<const Config> Get(const std::string &name) const; 用于从configset中获取一个config
1.3 Config

Config表达一个config,由config name和一组键值对构成的参数组成。需要实现的方法有

  • bool Exists(const std::string &key) 用于检查是否含有某一个键
  • template <typename T> bool Add(const std::string &key, T &&value, bool overwrite = false); 添加键值对到config中
  • bool Remove(const std::string &key); 去除键值对
  • template <typename T> T Get(const std::string &key) const; 输入键,获取值

此外,还应该确定注释的格式与解析方法。

// todo(congyu) UML图

基本的参数配置文件样例:config_test.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{  
  "test_config": {  
    "server_addr": "192.168.0.251",  
    "loop_times": "5"  
  },  
  "test2_config": {  
    "common": "generate target with all distances and all angles",  
    "_target_comment": "distance in m, angle in °",  
    "distance": "0.5,1.0,1.5,2.0,2.5",  
    "angle": "0,45,-45,90,-90"  
  },  
  "test3_config": {  
    "_common_targets_comment": "generate target with the i_th distance and the i_th angle",  
    "distance": "3.0",  
    "angle": "0"  
  }  
}

2. 使用

三个步骤:

  1. main读取文件,获得configset
  2. configset.Get 获得某一个config
  3. 经类的构造函数传递进去,然后通过config.Get方法获得参数赋值给成员变量
1
2
3
4
5
6
7
auto configs = trifo::config::ReadConfigs(config_file);

auto test_config = configs->Get("test_config");
auto user_class = UserClass(test_config);

auto test2_config = configs->Get("test2_config");
auto user_class2 = UserClass2(test2_config);
1
2
3
4
/**  
 * @brief Constructor. 
 */
 explicit UserClass(const std::shared_ptr<ConfigSet> &config);
1
2
3
4
UserClass::UserClass(const std::shared_ptr<ConfigSet> &config) {  
  server_addr_ = config->Get<std::string>("server_addr");
  loop_times_ = config->Get<int>("loop_times");
}

3. 一点优化

第一个问题:代码中在构造函数里面出现了有很多hard code,这是我们不希望看到的丑陋代码。第二个问题:此外还需要考虑一些异常情况,如果某个键获取失败时如何处理?

对于第一个问题,推荐使用constexpr 变量解决。对于第二个问题,可以新增一个get方法,该方法在输入键时,要求也输入一个默认值,如果从config的键值对中找不到该键,则返回该默认值。

config类新增方法:

1
2
3
4
5
template <typename T>  
typename std::remove_reference<T>::type Get(const std::string &key,  
                                            T &&default_value) const;  
template <typename T, typename U = T>  
T Get(const std::string &key, const U &default_value) const;

使用constexpr

1
2
3
4
5
6
namespace {
constexpr const char *kServerAddr = "server_addr";  
constexpr const char *kDefaultPredictTime = "192.168.0.1";
constexpr const char *kLoopTimes = "loop_times";  
constexpr int kDefaultLoopTimes = 6;
} // namespace

类的构造函数变为如下形式,不再有hard code

1
2
3
4
UserClass::UserClass(const std::shared_ptr<ConfigSet> &config) {  
  server_addr_ = config->Get<std::string>(kServerAddr, kDefaultPredictTime);
  loop_times_ = config->Get<int>(kLoopTimes, kDefaultLoopTimes);
}

Contact

Feel free to contact me windmillyucong@163.com anytime for anything.

License

Creative Commons BY-SA 4.0

CC0