我想根据某些条件从std::ifstream
或std::cin
中获取istream
的输入。
只要我能让它工作,我必须使用一个原始的std::istream*
指针:
int main(int argc, char const* argv[]) {
std::istream *in_stream;
std::ifstream file;
if (argc > 1) {
std::string filename = argv[1];
file.open(filename);
if (not file.is_open()) {
return -1;
}
in_stream = &file;
} else {
in_stream = &std::cin;
}
// do stuff with the input, regardless of the source...
}
是否有一种方法来重写上面使用智能指针?
这可能是关于所有权的问题:如何包装和传递两个不同的std::istream
实例,其中一个是拥有的,需要处置(在销毁时调用close()
)和所有权转移,而另一个是不由用户代码拥有(例如std::cin
),不能是delete
d。解决方案是多态性:创建一个像
struct StreamWrapper {
virtual std::istream &stream() = 0;
virtual ~StreamWrapper() = default;
};
和两个实现,例如OwnedStreamWrapper
和UnownedStreamWrapper
。前者可以直接包含std::istream
(可移动,但不可复制),后者可以包含(例如)std::istream&
,即对引用的流实例没有所有权。
struct OwnedStreamWrapper : public StreamWrapper {
template <typename... A>
OwnedStreamWrapper(A &&...a) : stream_{std::forward<A>(a)...} {}
std::istream &stream() override { return stream_; }
private:
std::ifstream stream_;
};
struct UnownedStreamWrapper : public StreamWrapper {
UnownedStreamWrapper(std::istream &stream) : stream_{stream} {}
std::istream &stream() override { return stream_; }
private:
std::istream &stream_;
};
除了流所有权之外,包装器所有权也很重要,可以用来在堆栈上自动处理包装器(和流)的生命周期,或者允许包装器的生命周期超过当前堆栈帧的生命周期。在下面的示例中,第一个ReadWrapper()
获得包装器的所有权,并且delete
自动获得它,而第二个ReadWrapper()
不获得所有权。在这两种情况下,多态性(即StreamWrapper
的实现)决定了底层流的处理方式,即它是否超出其包装器(如std::cin
应该)或与它一起死亡(如手动实例化的流应该)。
void ReadWrite(std::istream &in, std::ostream &out) {
std::array<char, 1024> buffer;
while (in) {
in.read(buffer.data(), buffer.size());
out.write(buffer.data(), in.gcount());
}
}
void ReadWrapper(std::unique_ptr<StreamWrapper> stream) {
ReadWrite(stream->stream(), std::cout);
}
void ReadWrapper(StreamWrapper &stream) {
ReadWrite(stream.stream(), std::cout);
}
下面是一个可运行示例中{owned | unnowned} {streams | stream wrappers}的所有四种组合:
#include <array>
#include <fstream>
#include <iostream>
#include <memory>
#include <utility>
namespace {
/********** The three snippets above go here! **********/
} // namespace
int main() {
OwnedStreamWrapper stream1{"/proc/cpuinfo"};
std::unique_ptr<StreamWrapper> stream2{
std::make_unique<OwnedStreamWrapper>("/proc/cpuinfo")};
std::ifstream stream3_file{"/proc/cpuinfo"}; // Let’s pretend it is unowned.
UnownedStreamWrapper stream3{stream3_file};
std::ifstream stream4_file{"/proc/cpuinfo"}; // Let’s pretend it is unowned.
std::unique_ptr<StreamWrapper> stream4{
std::make_unique<UnownedStreamWrapper>(stream4_file)};
ReadWrapper(stream1); // owned stream, wrapper kept
ReadWrapper(std::move(stream2)); // owned stream, wrapper transferred
ReadWrapper(stream3); // unowned stream, wrapper kept
ReadWrapper(std::move(stream4)); // unowned stream, wrapper transferred
}
智能指针有点不适合这种情况,因为它需要动态分配(在这种情况下可能没有必要)
1。在另一个函数
中进行处理正如@BoP在评论中指出的那样,处理这个问题的一个好方法是使用另一个函数来传递适当的输入流:
godbolt
int do_thing(std::istream& in_stream) {
// do stuff with the input, regardless of the source...
int foobar;
in_stream >> foobar;
return 0;
}
int main(int argc, char const* argv[]) {
std::ofstream{"/tmp/foobar"} << 1234;
if(argc > 1) {
std::ifstream file{argv[1]};
if(!file) return -1;
return do_thing(file);
} else {
return do_thing(std::cin);
}
}
2。切换流缓冲区
如果你不使用std::cin
,如果一个文件名传递,你可以改变std::cin
的流缓冲区与rdbuf()
的文件之一-这样你可以在两种情况下使用std::cin
。
godbolt
std::ifstream file;
std::streambuf* oldbuf = std::cin.rdbuf();
if (argc > 1) {
file.open(argv[1]);
if (!file) return -1;
// switch buffer of std::cin
std::cin.rdbuf(file.rdbuf());
}
// do stuff with the input, regardless of the source...
int foobar;
std::cin >> foobar; // this will reader either from std::cin or the file, depending on the current streambuffer assigned to std::cin
// restore old stream buffer
std::cin.rdbuf(oldbuf);
3。使用std::unique_ptr
/std::shared_ptr
和自定义删除器
如果你仍然想使用智能指针,最简单的解决方案是使用std::unique_ptr
/std::shared_ptr
和一个处理std::cin
情况的自定义删除器:
godbolt
// deletes the stream only if it is not std::cin
struct istream_ptr_deleter {
void operator()(std::istream* stream) {
if(&std::cin == stream) return;
delete stream;
}
};
using istream_ptr = std::unique_ptr<std::istream, istream_ptr_deleter>;
// Example Usage:
istream_ptr in_stream;
if (argc > 1) {
in_stream.reset(new std::ifstream(argv[1]));
if (!*in_stream) return -1;
} else {
in_stream.reset(&std::cin);
}
int foobar;
*in_stream >> foobar;