如何移动std :: ostream?

Die*_*ühl 11 c++ iostream c++11

由于设计std::ostream无法移动,因此问题变为:如何std::ostream移动以便可以写入不同的目的地?

基本目标是让一个工厂函数获取一个URI并返回一些东西,让我们调用它,omstream(输出可移动流)可以像下面这样使用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);
}
Run Code Online (Sandbox Code Playgroud)

omstream会负责妥善移动对象:

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
};
Run Code Online (Sandbox Code Playgroud)

问题实际上是实现什么omstream(或者甚至是相应的类模板basic_omstream)?

How*_*ant 7

你几乎做对了.你的例子是移动构建ios基地两次.你应该将直接基类.并假设有成员streambuf,也移动:

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
};
Run Code Online (Sandbox Code Playgroud)

我在set_rdbuf评论中将"get"更改为"install" .通常,这会将指向该成员的指针安装streambufios基类中.

当前非移动设计的移动和交换成员的istream/ostream设置是为了使移动和派生类的成员(例如ofstreamomstream)更直观.食谱是:

移动基础和成员,并在移动构造函数中设置rdbuf.

嵌入式rdbuf是整个层次结构的复杂因素.

  • <nod>这是令人困惑的领域.每次看到它我都要重新学习它. (4认同)
  • 我完全错过了`std :: basic_ostream <...>`的移动构造函数调用`std :: basic_ios <...> :: move()`.也就是说,对`std :: basic_ios <...>`默认构造函数的调用实际上不是从`std :: basic_ostream <...>`完成的,而是来自最派生类型,因为它是一个`虚拟`基础:27.7.3.2 [ostream.cons]第5段中的语句调用`std :: basic_ios <...>`的默认构造函数是派生流的契约的一部分! (2认同)

Die*_*ühl 5

霍华德答案中公布的代码是草稿(基于问题中的草案).霍华德的答案帮助解决了virtual基类的一个令人困惑的问题std::ios:在移动派生流时,基类需要默认构造,因为移动构造函数将使用std::ios显式移动流的一部分.这个答案只是填补了缺失的部分.std::ostreamstd::ios::move()

下面的实现维护了一个指向流缓冲区的指针,该缓冲区通常预期存在于堆上,并在破坏时将被释放std::unique_ptr<...>.因为它可能需要返回std::omstream一个长寿命的流,例如流缓冲区std::cout,将std::unique_ptr<...>被设置为使用缺失者,如果它可以什么也不做omstream不拥有的流缓冲区.

#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;
Run Code Online (Sandbox Code Playgroud)

除非相应的流超过了,否则使用std::ofstreamstd::ostringstream初始化a omstream不起作用omstream.通常,将分配相应的流缓冲区.omstream例如,可以像下面的代码一样使用该类,该代码基于给予合适的工厂函数的URI创建流:

#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";
}
Run Code Online (Sandbox Code Playgroud)

如果有其他可用的流缓冲区可以从URI构造,则可以将这些缓冲区添加到该make_stream()函数中.