编写可测试的代码 - lambda 函数和unique_ptr中的basic_istream工厂
Writing testable code - a basic_istream factory within lambda functions and unique_ptr
简介
这是在我还不自信的现代C++功能(如lambda函数和basic_istream
)的背景下评估我的方法的请求。最后我有一个简短的具体问题清单,所有这些问题都与同一个问题有关。
背景
我正在使用Boost::program_options
(1.69)在C++14中编写配置文件解析器,特别是boost::program_options::parse_config_file()
函数。此函数(在boost/program_options/parsers.hpp
中定义)有两个覆盖。
第一个从磁盘上的文件中读取配置:
basic_parsed_options<charT>
parse_config_file(const char* filename, const options_description&,
bool allow_unregistered = false);
第二个从std::basic_istream<charT>
引用中读取配置:
basic_parsed_options<charT>
parse_config_file(std::basic_istream<charT>&, const options_description&,
bool allow_unregistered = false);
我正在编写单元测试(使用 Google Test),以便我可以将配置文件的内容传递给函数,而不是像在生产中那样从实际文件中读取测试。这是为了避免必须创建临时配置文件,避免并行运行测试时发生冲突等。也许这种方法可能会更好,但是能够从字符串而不是文件提供配置似乎并没有错。
我还要补充一点,我不能简单地将 istream 传递给process_config
函数,因为在完整的实现中,文件名实际上是在函数本身中确定的。 即首先解析命令行,并从中获取配置文件名。因此,在执行函数之前,文件名是未知的。
实现
正在测试的函数最初如下所示:
struct AppConfig {
bool myoption1 {false};
};
void process_config(int argc, char * argv[], AppConfig & config) {
// ...
po::parse_config_file("config.cfg", config_file_options);
// ...
}
问题是这个接口没有提供注入配置文件内容的方法,所以我考虑传入一个process_config()
可以调用的回调,以便返回它可以传递给po::parse_config_file()
的basic_istream<charT>
。这样,测试可以传入一个回调,该回调仅提供预加载配置文件内容的std::istringstream
(basic_istream
模板的子类),并且生产客户端可以传入一个简单的回调,该回调只需打开磁盘上的指定文件并返回相应的 istream。
为此,我创建了这个:
template <typename funcF>
void process_config(int argc, char * argv[], AppConfig & config, funcF get_istream) {
namespace po = boost::program_options;
po::options_description config_file_options;
config_file_options.add_options() /* ... */;
auto istr_up = get_istream("config.cfg");
auto & istr = *istr_up.get();
po::parse_config_file(istr, config_file_options);
}
然后在生产代码中,我可以使用它从配置文件返回 ifstream:
auto make_config_istream(const std::string & s) {
return std::unique_ptr<std::ifstream>(s.c_str());
}
在测试代码中,我可以使用它从测试中定义的字符串返回配置文件内容:
std::unique_ptr<std::basic_istream<char>> make_istream(const std::string & s) {
return std::make_unique<std::istringstream>(s);
}
process_config(0, nullptr, config,
[](const std::string &){ return make_istream("foo=1nbar=2");} );
这似乎按照我的期望编译和执行。但是,由于 lambda 签名必须存在忽略const std::string &
才能与process_config
模板匹配,因此它似乎有点笨拙,因此我尝试添加这个额外的帮助程序函数来隐藏它:
auto make_config(const std::string & config) {
// the std::string parameter is ignored:
return [config](const std::string &) { return make_istream(config); };
}
然后将测试代码更改为:
process_config(0, nullptr, config, make_config("foo=1nbar=2"));
此语法将满足我的要求。
请注意,我将在唯一指针中传回istream
对象。我不是 100% 确定这是正确的方法,但由于 istream 的默认构造函数被禁用,我无法按值传递它们,所以似乎我必须在堆上分配它们并通过指针传递它们。
问题
感谢您阅读本文。我的问题是:
- 通过使用
unique_ptr
返回堆上新构建的istream
是否合适/最佳实践?有没有更好的方法可以做到这一点? - 取消引用unique_ptr以获取对
istream
引用的方法是否正确使用auto & istr = *istr_up.get()
?这对我来说闻起来很糟糕。 - 使用模板化
process_config
函数是处理get_istream
参数中涉及的类型的最佳方式吗?有没有更好的方法来用auto
做到这一点? - 最后,对于整个问题,有没有更好的方法?
通过使用unique_ptr在堆上返回新构建的 istream 是否合适/最佳实践?有没有更好的方法可以做到这一点?
basic_istream
是可移动的,则不需要unique_ptr
:
auto make_config_istream(const std::string & s) {
return std::ifstream(s);
}
auto make_istream(const std::string & s) {
return std::istringstream(s);
}
template <typename funcF>
void process_config(int argc, char * argv[], AppConfig & config, funcF get_istream) {
namespace po = boost::program_options;
po::options_description config_file_options;
config_file_options.add_options() /* ... */;
auto istr = get_istream("config.cfg");
po::parse_config_file(istr, config_file_options);
}
其余的可以保持原样。
取消引用unique_ptr以获取对 istream 引用的方法是否正确使用 auto &istr = *istr_up.get()?这对我来说闻起来很糟糕。
这是正确的,但不必要的复杂,因为auto & istr = *istr_up;
做同样的事情。您也可以像使用原始指针一样使用istr_up
(即带有->
和*
)。
使用模板化process_config函数是处理get_istream参数中涉及的类型的最佳方式吗?有没有更好的方法来用自动做到这一点?
是的,这是正确的方法,但需要将parse_config
移动到使用它的所有翻译单元共享的标头中,否则需要格外小心以在必要时正确显式实例化。
process_config
的用法对我来说似乎太复杂了,为什么不呢:
process_config(0, nullptr, config,
[](auto){return std::istringstream("foo=1nbar=2");})
和
process_config(0, nullptr, config,
[](auto s){return std::ifstream(s);})
- 为什么 std::unique 不调用 std::sort?
- CLANG 编译器 说:变量"PTR"可能未初始化
- 在以唯一ptr为值的C++映射中,动态内存何时会被销毁
- 将 ptr 传递给 ptr 到 A 作为参数传递给 A 的函数是不好的做法吗?
- 为共享 ptr 向量实现复制 c'tor?
- 字符和整数中 **(ptr+1) 的值差异
- C++:在不中断共享的情况下通过引用传递共享 PTR?
- 生成"unique"矩阵
- 如何将派生类从基 ptr 分配给 nlohmann::json
- 引用 std::shared:ptr 以避免引用计数
- 我对 std::unique(算法)C++有问题
- 为什么我不能在不进行任何转换的情况下将浮点数放入任何类型的 ptr 中?
- 在调用函数时,ptr** 和 ptr*& 之间是否有区别,或者首选C++?
- 另一种类型的智能ptr,比如具有弱refs的unique_ptr
- 尝试打印出 *ptr++ 的值,以了解它是如何工作的
- 如何控制共享 ptr 引用计数?
- std::shared_ptr::unique(),复制和线程安全
- 如何在C++03中用自定义谓词调用std::unique
- C++中的指针否定 (!ptr == NULL)
- C++14 unique_ptr并使用已删除的函数'std::unique-ptr' unique_ptr错误