是否有一种方法可以使一个智能指针指向std::ifstream或std::cin?



我想根据某些条件从std::ifstreamstd::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),不能是deleted。解决方案是多态性:创建一个像

这样的接口
struct StreamWrapper {
virtual std::istream &stream() = 0;
virtual ~StreamWrapper() = default;
};

和两个实现,例如OwnedStreamWrapperUnownedStreamWrapper。前者可以直接包含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;

相关内容

  • 没有找到相关文章

最新更新