Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can an std::ostream be moved?

Since it is by design that std::ostream can't be moved the question becomes: how can an std::ostream be moved such that it can write to different destinations?

The basic objective is to have a factory function taking a URI and returning something, let's call it, omstream (output movable stream) which can be used like an std::ostream:

omstream stream_factory(std::string const& uri);
void     process(std::ostream& out);

int main(int ac, char* av[]) {
    omstream destination{ stream_factory(ac == 2? av[1]: "example.txt") };
    process(destination);
}

The omstream would be responsible for properly moving the object:

class omstream
    : public std::ostream {
    // suitable members
public:
    omstream(/* suitable constructor arguments */);
    omstream(omstream&& other) // follow recipe of 27.9.1.11 [ofstream.cons] paragraph 4
        : std:ios(std::move(other))
        , std::ostream(std::move(other))
        // move any members {
        this->set_rdbuf(/* get the stream buffer */);
    }
    // other helpful or necessary members
};

The question is really what it takes to implement omstream (or, even a corresponding class template basic_omstream)?

like image 350
Dietmar Kühl Avatar asked Dec 25 '13 17:12

Dietmar Kühl


2 Answers

You've almost got it right. Your example is move constructing the ios base twice. You should move only the direct base class. And assuming there is member streambuf, move that too:

class omstream
    : public std::ostream {
    // suitable members
public:
    omstream(/* suitable constructor arguments */);
    omstream(omstream&& other) // follow recipe of 27.9.1.11 [ofstream.cons] paragraph 4
        : std: ostream(std::move(other)),
        // move any members {
        this->set_rdbuf(/* install the stream buffer */);
    }
    // other helpful or necessary members
};

I changed "get" to "install" in the set_rdbuf comment. Typically this installs a pointer to the member streambuf into the ios base class.

The current unorthodox design of the move and swap members of istream/ostream was set up to make the move and swap members of the derived classes (such as ofstream and omstream) more intuitive. The recipe is:

Move the base and members, and in the move constructor set the rdbuf.

It is that embedded rdbuf that is the complicating factor for the entire hierarchy.

like image 196
Howard Hinnant Avatar answered Sep 20 '22 00:09

Howard Hinnant


The code posted in Howard's answer is a draft (based on the draft posted in the question). Howard's answer helped resolving a confusing issue with the virtual base class std::ios: the base class needs to be default constructed when moving a derived stream as the std::ios portion of a stream will explicitly be moved by the std::ostream move constructor using std::ios::move(). This answer merely fills in the missing bits.

The implementation below maintains a pointer to a stream buffer which normally expected to live on the heap and will be released upon destruction with the help of std::unique_ptr<...>. As it may be desirable to return an std::omstream the stream buffer of a long-lived stream, e.g., std::cout, the std::unique_ptr<...> is set up to use a deleter which may do nothing if the omstream doesn't own the stream buffer.

#include <ostream>
#include <memory>
#include <utility>

template <typename cT, typename Traits = std::char_traits<cT>>
class basic_omstream
    : public std::basic_ostream<cT, Traits>
{
    using deleter = void (*)(std::basic_streambuf<cT, Traits>*);

    static void delete_sbuf(std::basic_streambuf<cT, Traits>* sbuf) {
        delete sbuf;
    }
    static void ignore_sbuf(std::basic_streambuf<cT, Traits>*) {
    }
    std::unique_ptr<std::basic_streambuf<cT, Traits>, deleter> m_sbuf;
public:
    basic_omstream()
        : std::basic_ios<cT, Traits>()
        , std::basic_ostream<cT, Traits>(nullptr)
        , m_sbuf(nullptr, &ignore_sbuf) {
    }
    basic_omstream(std::basic_streambuf<cT, Traits>* sbuf,
                   bool owns_streambuf)
        : std::basic_ios<cT, Traits>()
        , std::basic_ostream<cT, Traits>(sbuf)
        , m_sbuf(sbuf, owns_streambuf? &delete_sbuf: &ignore_sbuf) {
        this->set_rdbuf(this->m_sbuf.get());
    }
    basic_omstream(basic_omstream&& other)
        : std::basic_ios<cT, Traits>() // default construct ios!
        , std::basic_ostream<cT, Traits>(std::move(other))
        , m_sbuf(std::move(other.m_sbuf)) {
        this->set_rdbuf(this->m_sbuf.get());
    }
    basic_omstream& operator=(basic_omstream&& other) {
        this->std::basic_ostream<cT, Traits>::swap(other);
        this->m_sbuf.swap(other.m_sbuf);
        this->set_rdbuf(this->m_sbuf.get());
        return *this;
    }
};

typedef basic_omstream<char>    omstream;
typedef basic_omstream<wchar_t> womstream;

Using an std::ofstream or an std::ostringstream to initialize an omstream doesn't work unless the corresponding stream outlives the omstream. In general a corresponding stream buffer will be allocated. The class omstream could, e.g., be used like in the code below which create a stream based on an URI given to a suitable factory function:

#include <iostream>
#include <sstream>
#include <fstream>

omstream make_stream(std::string const& uri) {
    if (uri == "stream://stdout") {
        return omstream(std::cout.rdbuf(), false);
    }
    else if (uri == "stream://stdlog") {
        return omstream(std::clog.rdbuf(), false);
    }
    else if (uri == "stream://stderr") {
        return omstream(std::cerr.rdbuf(), false);
    }
    else if (uri.substr(0, 8) == "file:///") {
        std::unique_ptr<std::filebuf> fbuf(new std::filebuf);
        fbuf->open(uri.substr(8), std::ios_base::out);
        return omstream(fbuf.release(), true);
    }
    else if (uri.substr(0, 9) == "string://") {
        return omstream(new std::stringbuf(uri.substr(9)), true);
    }
    throw std::runtime_error("unknown URI: '" + uri + "'");
}

int main(int ac, char* av[])
{
    omstream out{ make_stream(ac == 2? av[1]: "stream://stdout") };
    out << "hello, world\n";
}

If there are other stream buffers available which could be constructed from a URI, these could be added to the make_stream() function.

like image 30
Dietmar Kühl Avatar answered Sep 23 '22 00:09

Dietmar Kühl