处理 C++ iostream 时的最佳实践
Best practice when dealing with C++ iostreams
我正在编写一个用于某些文本处理的命令行实用程序。我需要一个(或两个)辅助函数来执行以下操作:
- 如果文件名是
-
,return标准input/output;
- 否则,创建并打开一个文件,检查错误,然后 return 它。
我的问题来了:design/implement 这样一个函数的最佳实践是什么?它应该是什么样子?
我先考虑老派FILE*
:
FILE *open_for_read(const char *filename)
{
if (strcmp(filename, "-") == 0)
{
return stdin;
}
else
{
auto fp = fopen(filename, "r");
if (fp == NULL)
{
throw runtime_error(filename);
}
return fp;
}
}
它有效,以后 fclose(stdin)
是安全的(以防万一有人忘记了),但那样我将无法访问流方法,例如 std::getline
.
所以我认为,现代 C++ 方法是对流使用智能指针。一开始,我试过
unique_ptr<istream> open_for_read(const string& filename);
这适用于 ifstream
但不适用于 cin
,因为您无法删除 cin
。所以我必须为 cin
案例提供一个自定义删除器(什么都不做)。但是突然间,它无法编译,因为显然,当提供自定义删除器时,unique_ptr
变成了不同的类型。
最终,经过多次调整和在 Whosebug 上的搜索,这是我能想到的最好的:
unique_ptr<istream, void (*)(istream *)> open_for_read(const string &filename)
{
if (filename == "-")
{
return {static_cast<istream *>(&cin), [](istream *) {}};
}
else
{
unique_ptr<istream, void (*)(istream *)> pifs{new ifstream(filename), [](istream *is)
{
delete static_cast<ifstream *>(is);
}};
if (!pifs->good())
{
throw runtime_error(filename);
}
return pifs;
}
}
它是类型安全和内存安全的(或者至少我是这样认为的;如果我错了请纠正我),但这看起来有点丑陋和样板化,最重要的是,它让人头疼让它编译。
我是不是做错了,漏掉了什么?一定有更好的方法。
我可能会进入
std::istream& open_for_read(std::ifstream& ifs, const std::string& filename) {
return filename == "-" ? std::cin : (ifs.open(filename), ifs);
}
然后向函数提供 ifstream
。
std::ifstream ifs;
auto& is = open_for_read(ifs, the_filename);
// now use `is` everywhere:
if(!is) { /* error */ }
while(std::getline(is, line)) {
// ...
}
ifs
将在打开时像往常一样在超出范围时关闭。
投掷版本可能如下所示:
std::istream& open_for_read(std::ifstream& ifs, const std::string& filename) {
if(filename == "-") return std::cin;
ifs.open(filename);
if(!ifs) throw std::runtime_error(filename + ": " + std::strerror(errno));
return ifs;
}
作为 Ted 答案的替代方案(实际上我认为我更喜欢),您可以让您的自定义删除器更智能一些:
auto stream_deleter = [] (std::istream *stream) { if (stream != &std::cin) delete stream; };
using stream_ptr = std::unique_ptr <std::istream, decltype (stream_deleter)>;
stream_ptr open_for_read (const std::string& filename)
{
if (filename == "-")
return stream_ptr (&std::cin, stream_deleter);
auto sp = stream_ptr (new std::ifstream (filename), stream_deleter);
if (!sp->good ())
throw std::runtime_error (filename);
return sp;
}
然后相同的删除器适用于这两种情况,并且没有输入问题。
我过去使用的方法是调用 rdbuf 来更改 std::cin
的缓冲区。如果您不想使用 std::cin
更改现有代码,这可能很有用。你必须注意不要在它被销毁后使用缓冲区,但是,这不是 RAII 包装器无法解决的。类似的东西(未经测试,甚至没有被证明是正确的):
struct stream_redirector {
stream_redirector(std::iostream& s, std::string const& filename,
std::ios_base::openmode mode = ios_base::in)
: redirected_stream_{s}
{
if (filename != "-") {
stream_.open(filename, mode);
if (stream_) {
throw std::runtime_error(filename + ": " + std::strerror(errno));
saved_buf_ = redirected_stream_.rdbuf();
redirected_stream_.rdbuf(stream_.rdbuf());
}
}
~stream_redirector() {
if (saved_buf_ != nullptr) {
redirected_stream_.rdbuf(saved_buf_);
}
}
private:
std::stream& redirected_stream_;
std::streambuf* saved_buf_{nullptr};
std::fstream stream_;
};
待用:
...
stream_redirector cin_redirector(std::cin, filename);
std::string str;
std::cin >> str;
...
我正在编写一个用于某些文本处理的命令行实用程序。我需要一个(或两个)辅助函数来执行以下操作:
- 如果文件名是
-
,return标准input/output; - 否则,创建并打开一个文件,检查错误,然后 return 它。
我的问题来了:design/implement 这样一个函数的最佳实践是什么?它应该是什么样子?
我先考虑老派FILE*
:
FILE *open_for_read(const char *filename)
{
if (strcmp(filename, "-") == 0)
{
return stdin;
}
else
{
auto fp = fopen(filename, "r");
if (fp == NULL)
{
throw runtime_error(filename);
}
return fp;
}
}
它有效,以后 fclose(stdin)
是安全的(以防万一有人忘记了),但那样我将无法访问流方法,例如 std::getline
.
所以我认为,现代 C++ 方法是对流使用智能指针。一开始,我试过
unique_ptr<istream> open_for_read(const string& filename);
这适用于 ifstream
但不适用于 cin
,因为您无法删除 cin
。所以我必须为 cin
案例提供一个自定义删除器(什么都不做)。但是突然间,它无法编译,因为显然,当提供自定义删除器时,unique_ptr
变成了不同的类型。
最终,经过多次调整和在 Whosebug 上的搜索,这是我能想到的最好的:
unique_ptr<istream, void (*)(istream *)> open_for_read(const string &filename)
{
if (filename == "-")
{
return {static_cast<istream *>(&cin), [](istream *) {}};
}
else
{
unique_ptr<istream, void (*)(istream *)> pifs{new ifstream(filename), [](istream *is)
{
delete static_cast<ifstream *>(is);
}};
if (!pifs->good())
{
throw runtime_error(filename);
}
return pifs;
}
}
它是类型安全和内存安全的(或者至少我是这样认为的;如果我错了请纠正我),但这看起来有点丑陋和样板化,最重要的是,它让人头疼让它编译。
我是不是做错了,漏掉了什么?一定有更好的方法。
我可能会进入
std::istream& open_for_read(std::ifstream& ifs, const std::string& filename) {
return filename == "-" ? std::cin : (ifs.open(filename), ifs);
}
然后向函数提供 ifstream
。
std::ifstream ifs;
auto& is = open_for_read(ifs, the_filename);
// now use `is` everywhere:
if(!is) { /* error */ }
while(std::getline(is, line)) {
// ...
}
ifs
将在打开时像往常一样在超出范围时关闭。
投掷版本可能如下所示:
std::istream& open_for_read(std::ifstream& ifs, const std::string& filename) {
if(filename == "-") return std::cin;
ifs.open(filename);
if(!ifs) throw std::runtime_error(filename + ": " + std::strerror(errno));
return ifs;
}
作为 Ted 答案的替代方案(实际上我认为我更喜欢),您可以让您的自定义删除器更智能一些:
auto stream_deleter = [] (std::istream *stream) { if (stream != &std::cin) delete stream; };
using stream_ptr = std::unique_ptr <std::istream, decltype (stream_deleter)>;
stream_ptr open_for_read (const std::string& filename)
{
if (filename == "-")
return stream_ptr (&std::cin, stream_deleter);
auto sp = stream_ptr (new std::ifstream (filename), stream_deleter);
if (!sp->good ())
throw std::runtime_error (filename);
return sp;
}
然后相同的删除器适用于这两种情况,并且没有输入问题。
我过去使用的方法是调用 rdbuf 来更改 std::cin
的缓冲区。如果您不想使用 std::cin
更改现有代码,这可能很有用。你必须注意不要在它被销毁后使用缓冲区,但是,这不是 RAII 包装器无法解决的。类似的东西(未经测试,甚至没有被证明是正确的):
struct stream_redirector {
stream_redirector(std::iostream& s, std::string const& filename,
std::ios_base::openmode mode = ios_base::in)
: redirected_stream_{s}
{
if (filename != "-") {
stream_.open(filename, mode);
if (stream_) {
throw std::runtime_error(filename + ": " + std::strerror(errno));
saved_buf_ = redirected_stream_.rdbuf();
redirected_stream_.rdbuf(stream_.rdbuf());
}
}
~stream_redirector() {
if (saved_buf_ != nullptr) {
redirected_stream_.rdbuf(saved_buf_);
}
}
private:
std::stream& redirected_stream_;
std::streambuf* saved_buf_{nullptr};
std::fstream stream_;
};
待用:
...
stream_redirector cin_redirector(std::cin, filename);
std::string str;
std::cin >> str;
...