4

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 management of the ostream from the main. So I was thinking to write a class, but I'm a bit confused about the design. I have in mind two solution

  1. (publicly) Subclass ostream: it this way I would have all method of ostream. However here the main problem would be the creator:

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

because I should initialize ostream depending on filename, and apparently is impossible.

  1. Create a class having osteram as a member and overload operator<<.

I'm sure that there are other, more elegant solution to this problem. Which design would you suggest?

Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
MaPo
  • 613
  • 4
  • 9
  • 1
    Why not just a pointer to the appropriate `ostream` object? Or if you want to get fancy, just instantiate a plain `std::ostream` and put in it the appropriate `streambuf` (either the one from `cout` of an appropriately instantiated `std::filebuf`). – Matteo Italia Jun 10 '19 at 06:24

2 Answers2

3

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.

anarthal
  • 156
  • 4
  • Thank you for your solution. However I'm baffled by a small modification: if in the main I use `std::ostream& output {*create_stream(false)};`everything works fine, while with`std::ostream output {*create_stream(false)};` I get the error concerning a deleted function of `basic_ostream`. It is because the copy constructor of this last class is deleted? In this case why doesn't it move automatically? – MaPo Jun 13 '19 at 19:22
  • std::ostream& output {*create_stream(false)}; This may seem to work for to_file=false. But if you pass in to_file=true, you will have a dangling reference, which causes undefined behaviour (a crash in the best scenario). The thing here is that the type you are returning from the function is a unique_ptr. As long as the returned variable lives, the newly created ostream does (on the case of to_file=true). By doing the first, you are saving just a reference to the object, but not the unique_ptr. After being returned from the function, it gets destroyed (it is a temporary). – anarthal Jun 14 '19 at 21:15
  • For the second one: when dereferencing the unique_ptr, you are invoking std::unique_ptr::operator* (https://en.cppreference.com/w/cpp/memory/unique_ptr/operator*), which returns a const ostream&, not a ostream&&, even if the unique_ptr is a reference to rvalue. This is why you are getting the error. – anarthal Jun 14 '19 at 21:21
  • Also consider that the real type of the ostream returned when to_file=true is a std::ofstream, derived from std::ostream. If you created a ostream from a ofstream, you would be missing part of the actual object (only member variables for ostream would be copied/moved, not the ones in ofstream). This is called slicing, and is usually bad. – anarthal Jun 14 '19 at 21:23
3

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.

Artyer
  • 31,034
  • 3
  • 47
  • 75