如何设计简单易用的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解析应该包含一下几个部分:
- 文件的读取与写出
- ConfigSet的实现
- 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中删掉某个configstd::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. 使用
三个步骤:
- main读取文件,获得configset
- configset.Get 获得某一个config
- 经类的构造函数传递进去,然后通过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.