ostream: class that outputs either on cout or on a file

后端 未结 2 1058
天命终不由人
天命终不由人 2021-01-24 02:38

I need to write a program that output either to the std::cout or to some file. I was reading this post to see how to do. However I would like to separate the manage

相关标签:
2条回答
  • 2021-01-24 02:52

    Since they both inherit from std::ostream, you can just assign it to a std::ostream&.

    In your case, you can simply do something like this:

    #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.
    }
    

    A simpler approach would be to not worry about this at all. Just put all of your code that outputs stuff inside one function that takes a std::ostream& parameter, and call it with a std::ofstream or another 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);
        }
    }
    

    If you want to be able to return the object without the file closing and becoming a dangling reference, you need to store it somewhere. This example stores it in a struct:

    #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();
    }
    

    I also came up with another solution that uses std::unique_ptr so that you can use any derived class of std::ostream, but that unnecessarily uses dynamic memory if you only want an existing std::ostream (Like std::cout) or a std::fstream. See here.

    0 讨论(0)
  • 2021-01-24 02:59

    I would try to split here the stream creation with the stream usage. std::ostream is already polymorphic, so as long as you pass a reference or pointer to the function that uses the stream, all good.

    For the creation, I would go for creating the stream in the heap, as the post you linked to suggests. However, doing explicit memory management (raw new/delete) is dangerous, so I would use a smart pointer, like 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);
    }
    

    I've used a custom deleter with std::unique_ptr. The reason for that is: if we are using the file, I want the stream to be deleted; but std::cout is a global object, which we must not delete. The agreement here is that when your OstreamPtr gets destroyed, ConditionalDeleter::operator() will get called. *streamptr returns you a reference to your std::ostream, which you can use as you want.

    Please note you need C++11 support to use this solution.

    0 讨论(0)
提交回复
热议问题