【问题标题】:ostream: class that outputs either on cout or on a fileostream:在 cout 或文件上输出的类
【发布时间】:2019-10-24 13:18:50
【问题描述】:

我需要编写一个输出到std::cout 或某个文件的程序。我正在阅读this post 以了解该怎么做。但是,我想将 ostream 的管理与main 分开。所以我想写一个类,但我对设计有点困惑。我想到了两个解决方案

  1. (公开)子类 ostream:这样我就拥有了 ostream 的所有方法。然而这里的主要问题是创建者:

    class sw_ostream : public ostream {
       sw_ostream (cost char* filename) : ostream ( \\? ) {
       \\ ...
       }
    \\...
    }
    

因为我应该根据filename初始化ostream,显然是不可能的。

  1. 创建一个以 osteram 作为成员并重载 operator<< 的类。

我确信对于这个问题还有其他更优雅的解决方案。您会建议哪种设计?

【问题讨论】:

  • 为什么不只是指向适当的ostream 对象的指针?或者,如果您想变得花哨,只需实例化一个普通的std::ostream 并将适当的streambuf 放入其中(来自cout 的一个适当实例化的std::filebuf)。

标签: c++ ostream


【解决方案1】:

我会尝试将流创建与流使用分开。 std::ostream 已经是多态的,所以只要将引用或指针传递给使用流的函数,一切都很好。

对于创建,我会在堆中创建流,正如您链接到的帖子所建议的那样。但是,进行显式内存管理(原始的新/删除)是危险的,所以我会使用智能指针,例如 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 为您返回对您的 std::ostream 的引用,您可以随意使用它。

请注意,您需要 C++11 支持才能使用此解决方案。

【讨论】:

  • 感谢您的解决方案。然而,我对一个小修改感到困惑:如果我主要使用std::ostream&amp; output {*create_stream(false)};everything 工作正常,而使用std::ostream output {*create_stream(false)}; 我收到有关basic_ostream 已删除函数的错误。是因为这最后一个类的拷贝构造函数被删除了?这种情况下为什么不自动移动?
  • std::ostream& 输出 {*create_stream(false)};这似乎适用于 to_file=false。但是如果你传入 to_file=true,你将有一个悬空引用,这会导致未定义的行为(最好的情况是崩溃)。这里的问题是您从函数返回的类型是 unique_ptr。只要返回的变量存在,新创建的 ostream 就会存在(在 to_file=true 的情况下)。通过执行第一个操作,您只保存了对对象的引用,而不是 unique_ptr。从函数返回后,它被销毁(它是临时的)。
  • 第二个:当取消引用 unique_ptr 时,您正在调用 std::unique_ptr::operator* (en.cppreference.com/w/cpp/memory/unique_ptr/operator*),它返回一个 const ostream&,而不是一个 ostream&&,即使 unique_ptr是对右值的引用。这就是您收到错误的原因。
  • 还要考虑当 to_file=true 时返回的 ostream 的真实类型是从 std::ostream 派生的 std::ofstream。如果您从 ofstream 创建了一个 ostream,您将丢失部分实际对象(只有 ostream 的成员变量会被复制/移动,而不是 ofstream 中的成员变量)。这称为切片,通常很糟糕。
【解决方案2】:

由于它们都继承自 std::ostream,因此您只需将其分配给 std::ostream&amp;

在你的情况下,你可以简单地做这样的事情:

#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&amp; 参数的函数中,然后使用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);
    }
}

如果您希望能够在不关闭文件并成为悬空引用的情况下返回对象,则需要将其存储在某个地方。此示例将其存储在结构中:

#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 的任何派生类,但是如果您只想要现有的std::ostream(如std::cout)或std::fstreamSee here.

【讨论】:

    猜你喜欢
    • 2017-01-18
    • 2013-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-26
    • 1970-01-01
    相关资源
    最近更新 更多