简体   繁体   中英

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 )?

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.

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, eg, 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, eg, 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM