【问题标题】:How can an std::ostream be moved?如何移动 std::ostream?
【发布时间】:2014-01-13 12:46:13
【问题描述】:

由于std::ostream can't be moved 是设计使然,问题就变成了:如何移动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);
}

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
};

问题真的是实现omstream(或者,甚至是相应的类模板basic_omstream)需要什么?

【问题讨论】:

  • 如果我错了,请原谅我,但在右值引用上调用 std::move 是不是有点……冗余?
  • @chris:你的意思是std::move(other)?好吧,当一个对象有了名字时,它就不是右值了。在参数列表中,它基本上表示可以将右值传递给函数,但在函数内,参数是左值。
  • 隐式私有继承是有意的? (从未见过它隐含地写出来..)
  • 第一句是o型吗?链接的问题说 ostream 不可移动。它确实notofstream 是不可移动的。 ofstream可移动的。

标签: c++ c++11 iostream


【解决方案1】:

你几乎做对了。您的示例是移动构造 ios 基础两次。您应该移动 direct 基类。假设有成员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
};

我在set_rdbuf 评论中将“get”更改为“install”。通常,这会将指向成员 streambuf 的指针安装到 ios 基类中。

istream/ostream 的移动和交换成员的当前非正统设计被设置为使派生类(例如ofstreamomstream)的移动和交换成员更直观。配方是:

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

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

【讨论】:

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

霍华德答案中发布的代码是草稿(基于问题中发布的草稿)。霍华德的回答帮助解决了与virtual 基类std::ios 混淆的问题:移动派生流时需要默认构造基类,因为std::ios 流的部分将由std::ostream 移动显式移动使用std::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;

使用std::ofstreamstd::ostringstream 来初始化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";
}

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

【讨论】:

    猜你喜欢
    • 2014-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-14
    相关资源
    最近更新 更多