ostream:class 在 cout 或文件上输出
ostream: class that outputs either on cout or on a file
我需要编写一个程序,输出到 std::cout
或某个文件。我正在阅读 this post 以了解如何做。但是我想将 ostream 的管理与 main
分开。所以我想写一个class,但我对设计有点困惑。我想到了两个解决方案
(publicly) Subclass ostream:这样我就拥有了 ostream 的所有方法。然而这里的主要问题是创作者:
class sw_ostream : public ostream {
sw_ostream (cost char* filename) : ostream ( \? ) {
\ ...
}
\...
}
因为我应该根据 filename
初始化 ostream
,显然是不可能的。
- 创建一个 class 以 osteram 作为成员并重载
operator<<
。
我相信还有其他更优雅的解决方案可以解决这个问题。您会推荐哪种设计?
我会尝试在此处将流创建与流使用分开。 std::ostream 已经是多态的,所以只要将引用或指针传递给使用流的函数就可以了。
对于创建,我会在堆中创建流,正如您链接到的 post 所建议的那样。但是,进行显式内存管理(原始 new/delete)是危险的,所以我会使用智能指针,例如 std::unique_ptr:
#include <fstream>
#include <memory>
struct ConditionalDeleter
{
bool must_delete;
void operator()(std::ostream* os) const { if (must_delete) delete os; }
};
using OstreamPtr = std::unique_ptr<std::ostream, ConditionalDeleter>;
OstreamPtr create_stream(bool to_file)
{
if (to_file)
return OstreamPtr { new std::ofstream {"myfile.txt"}, ConditionalDeleter {true} };
else
return OstreamPtr { &std::cout, ConditionalDeleter {false} };
}
void use_stream(std::ostream& os)
{
os << "Hello world!" << std::endl;
}
int main()
{
auto streamptr = create_stream(false);
use_stream(*streamptr);
}
我在 std::unique_ptr 中使用了自定义删除器。这样做的原因是:如果我们正在使用该文件,我希望删除该流;但是 std::cout 是一个全局对象,我们不能删除它。这里的协议是当你的 OstreamPtr 被销毁时, ConditionalDeleter::operator() 将被调用。 *streamptr returns 你对你的 std::ostream 的引用,你可以随意使用它。
请注意,您需要 C++11 支持才能使用此解决方案。
由于它们都继承自 std::ostream
,您可以将其分配给 std::ostream&
。
对于你的情况,你可以简单地做这样的事情:
#include <iostream>
#include <fstream>
void do_stuff(const char* filename = nullptr) {
std::ofstream _f;
std::ostream& os = filename ? (_f.open(filename), _f) : std::cout;
os << "Output normally";
// If you want to check if it is a file somewhere else
if (std::ofstream* fp = dynamic_cast<std::ofstream*>(&os)) {
std::ofstream& f = *fp;
// But here you can probably check the condition used to make the file
// (e.g. here `filename != nullptr`)
}
// After returning, `os` is invalid because `_f` dies, so you can't return it.
}
一个更简单的方法是根本不用担心这个。只需将所有输出内容的代码放入一个带有 std::ostream&
参数的函数中,然后使用 std::ofstream
或另一个 std::ostream
:
调用它
void do_stuff(std::ostream& os) {
os << "Write string\n";
}
int main() {
if (using_file) {
std::ofstream f("filename");
do_stuff(f);
} else {
do_stuff(std::cout);
}
}
如果您希望能够 return 对象而不关闭文件并成为悬空引用,您需要将其存储在某个地方。此示例将其存储在结构中:
#include <iostream>
#include <fstream>
#include <utility>
#include <new>
#include <cassert>
struct sw_ostream {
private:
// std::optional<std::fstream> f;
// Use raw storage and placement new pre-C++17 instead of std::optional
alignas(std::fstream) unsigned char f[sizeof(std::fstream)];
std::ostream* os;
bool did_construct_fstream() const noexcept {
// If `os` is a pointer to `f`, we placement new`d, so we need to destruct it
return reinterpret_cast<unsigned char*>(os) == f;
}
// Destroys currently held std::fstream
// (Must have been constructed first and have `os` point to it)
void destruct() noexcept {
static_cast<std::fstream&>(*os).~basic_fstream();
}
public:
sw_ostream() = default;
sw_ostream(std::ostream& os_) : os(&os_) {}
template<class... Args>
explicit sw_ostream(Args&&... args) {
os = new (f) std::fstream(std::forward<Args>(args)...);
}
sw_ostream(std::fstream&& f) : os(nullptr) {
*this = std::move(f);
}
sw_ostream(sw_ostream&& other) noexcept {
*this = std::move(other);
}
sw_ostream& operator=(sw_ostream&& other) {
if (did_construct_fstream()) {
if (other.did_construct_fstream()) {
static_cast<std::fstream&>(*os) = std::move(static_cast<std::fstream&>(*(other.os)));
} else {
destruct();
os = other.os;
}
} else {
if (other.did_construct_fstream()) {
os = new (f) std::fstream(std::move(static_cast<std::fstream&>(*other.os)));
} else {
os = other.os;
}
}
return *this;
}
sw_ostream& operator=(std::ostream& other) {
if (did_construct_fstream()) {
destruct();
}
os = &other;
return *this;
}
sw_ostream& operator=(std::fstream&& other) {
if (did_construct_fstream()) {
static_cast<std::fstream&>(*os) = std::move(other);
} else {
os = new (f) std::fstream(std::move(other));
}
return *this;
}
std::ostream& operator*() const noexcept {
return *os;
}
std::ostream* operator->() const noexcept {
return os;
}
operator std::ostream&() const noexcept {
return *os;
}
std::fstream* get_fstream() const noexcept {
if (did_construct_fstream()) return &static_cast<std::fstream&>(*os);
return dynamic_cast<std::fstream*>(os);
}
// `s << (...)` is a shorthand for `*s << (...)` (Where `s` is a `sw_ostream`)
template<class T>
const sw_ostream& operator<<(T&& o) const {
*os << std::forward<T>(o);
return *this;
}
template<class T>
sw_ostream& operator<<(T&& o) {
*os << std::forward<T>(o);
return *this;
}
~sw_ostream() {
if (did_construct_fstream()) {
destruct();
}
}
};
int main() {
sw_ostream s;
if (opening_file) {
s = std::fstream("filename");
} else {
s = std::cout;
}
if (std::fstream* fp = s.get_fstream()) {
assert(fp->is_open());
}
s << "Hello, world!\n";
s->flush();
}
我还提出了另一个使用 std::unique_ptr
的解决方案,这样您就可以使用 std::ostream
的任何派生 class,但是如果您只想要一个现有的,则不必使用动态内存std::ostream
(如 std::cout
)或 std::fstream
。 See here.
我需要编写一个程序,输出到 std::cout
或某个文件。我正在阅读 this post 以了解如何做。但是我想将 ostream 的管理与 main
分开。所以我想写一个class,但我对设计有点困惑。我想到了两个解决方案
(publicly) Subclass ostream:这样我就拥有了 ostream 的所有方法。然而这里的主要问题是创作者:
class sw_ostream : public ostream { sw_ostream (cost char* filename) : ostream ( \? ) { \ ... } \... }
因为我应该根据 filename
初始化 ostream
,显然是不可能的。
- 创建一个 class 以 osteram 作为成员并重载
operator<<
。
我相信还有其他更优雅的解决方案可以解决这个问题。您会推荐哪种设计?
我会尝试在此处将流创建与流使用分开。 std::ostream 已经是多态的,所以只要将引用或指针传递给使用流的函数就可以了。
对于创建,我会在堆中创建流,正如您链接到的 post 所建议的那样。但是,进行显式内存管理(原始 new/delete)是危险的,所以我会使用智能指针,例如 std::unique_ptr:
#include <fstream>
#include <memory>
struct ConditionalDeleter
{
bool must_delete;
void operator()(std::ostream* os) const { if (must_delete) delete os; }
};
using OstreamPtr = std::unique_ptr<std::ostream, ConditionalDeleter>;
OstreamPtr create_stream(bool to_file)
{
if (to_file)
return OstreamPtr { new std::ofstream {"myfile.txt"}, ConditionalDeleter {true} };
else
return OstreamPtr { &std::cout, ConditionalDeleter {false} };
}
void use_stream(std::ostream& os)
{
os << "Hello world!" << std::endl;
}
int main()
{
auto streamptr = create_stream(false);
use_stream(*streamptr);
}
我在 std::unique_ptr 中使用了自定义删除器。这样做的原因是:如果我们正在使用该文件,我希望删除该流;但是 std::cout 是一个全局对象,我们不能删除它。这里的协议是当你的 OstreamPtr 被销毁时, ConditionalDeleter::operator() 将被调用。 *streamptr returns 你对你的 std::ostream 的引用,你可以随意使用它。
请注意,您需要 C++11 支持才能使用此解决方案。
由于它们都继承自 std::ostream
,您可以将其分配给 std::ostream&
。
对于你的情况,你可以简单地做这样的事情:
#include <iostream>
#include <fstream>
void do_stuff(const char* filename = nullptr) {
std::ofstream _f;
std::ostream& os = filename ? (_f.open(filename), _f) : std::cout;
os << "Output normally";
// If you want to check if it is a file somewhere else
if (std::ofstream* fp = dynamic_cast<std::ofstream*>(&os)) {
std::ofstream& f = *fp;
// But here you can probably check the condition used to make the file
// (e.g. here `filename != nullptr`)
}
// After returning, `os` is invalid because `_f` dies, so you can't return it.
}
一个更简单的方法是根本不用担心这个。只需将所有输出内容的代码放入一个带有 std::ostream&
参数的函数中,然后使用 std::ofstream
或另一个 std::ostream
:
void do_stuff(std::ostream& os) {
os << "Write string\n";
}
int main() {
if (using_file) {
std::ofstream f("filename");
do_stuff(f);
} else {
do_stuff(std::cout);
}
}
如果您希望能够 return 对象而不关闭文件并成为悬空引用,您需要将其存储在某个地方。此示例将其存储在结构中:
#include <iostream>
#include <fstream>
#include <utility>
#include <new>
#include <cassert>
struct sw_ostream {
private:
// std::optional<std::fstream> f;
// Use raw storage and placement new pre-C++17 instead of std::optional
alignas(std::fstream) unsigned char f[sizeof(std::fstream)];
std::ostream* os;
bool did_construct_fstream() const noexcept {
// If `os` is a pointer to `f`, we placement new`d, so we need to destruct it
return reinterpret_cast<unsigned char*>(os) == f;
}
// Destroys currently held std::fstream
// (Must have been constructed first and have `os` point to it)
void destruct() noexcept {
static_cast<std::fstream&>(*os).~basic_fstream();
}
public:
sw_ostream() = default;
sw_ostream(std::ostream& os_) : os(&os_) {}
template<class... Args>
explicit sw_ostream(Args&&... args) {
os = new (f) std::fstream(std::forward<Args>(args)...);
}
sw_ostream(std::fstream&& f) : os(nullptr) {
*this = std::move(f);
}
sw_ostream(sw_ostream&& other) noexcept {
*this = std::move(other);
}
sw_ostream& operator=(sw_ostream&& other) {
if (did_construct_fstream()) {
if (other.did_construct_fstream()) {
static_cast<std::fstream&>(*os) = std::move(static_cast<std::fstream&>(*(other.os)));
} else {
destruct();
os = other.os;
}
} else {
if (other.did_construct_fstream()) {
os = new (f) std::fstream(std::move(static_cast<std::fstream&>(*other.os)));
} else {
os = other.os;
}
}
return *this;
}
sw_ostream& operator=(std::ostream& other) {
if (did_construct_fstream()) {
destruct();
}
os = &other;
return *this;
}
sw_ostream& operator=(std::fstream&& other) {
if (did_construct_fstream()) {
static_cast<std::fstream&>(*os) = std::move(other);
} else {
os = new (f) std::fstream(std::move(other));
}
return *this;
}
std::ostream& operator*() const noexcept {
return *os;
}
std::ostream* operator->() const noexcept {
return os;
}
operator std::ostream&() const noexcept {
return *os;
}
std::fstream* get_fstream() const noexcept {
if (did_construct_fstream()) return &static_cast<std::fstream&>(*os);
return dynamic_cast<std::fstream*>(os);
}
// `s << (...)` is a shorthand for `*s << (...)` (Where `s` is a `sw_ostream`)
template<class T>
const sw_ostream& operator<<(T&& o) const {
*os << std::forward<T>(o);
return *this;
}
template<class T>
sw_ostream& operator<<(T&& o) {
*os << std::forward<T>(o);
return *this;
}
~sw_ostream() {
if (did_construct_fstream()) {
destruct();
}
}
};
int main() {
sw_ostream s;
if (opening_file) {
s = std::fstream("filename");
} else {
s = std::cout;
}
if (std::fstream* fp = s.get_fstream()) {
assert(fp->is_open());
}
s << "Hello, world!\n";
s->flush();
}
我还提出了另一个使用 std::unique_ptr
的解决方案,这样您就可以使用 std::ostream
的任何派生 class,但是如果您只想要一个现有的,则不必使用动态内存std::ostream
(如 std::cout
)或 std::fstream
。 See here.