如何在 C++ 中编写 ofstream 向量,它接收所有不同的输出流,如 cout、字符串流和 ofstream

How to write vector of ostreams in C++ which takes in all the different output streams like cout, ostringstream and ofstream

我正在尝试实现一个记录器,它可以注册到多个流,如 ostringstream、ofstream 等。我试图实现这样的功能

void register_stream(std::ostream& a);

向量如下

std::vector<std::ostream> streams;

寄存器流和运算符重载如下

void logger::register_stream(std::ostream &a)`

{

    streams.push_back(a);

}

template <typename T>

void logger::operator<<(T const& value)

{

    for (auto stream : streams)

    {

        (stream) << value;

    }

}

我正在尝试实现一个记录器,以在单个运算符“<<”调用上写入所有已注册的流。

调用代码如下:

std::ostringstream os;
    std::ofstream f;
    logger l;
    l.register_stream(f);
    l.register_stream(os);
    l << "log this";

我收到一个错误:

C2280: std::basic_ostream<char,std::char_traits<char>>::basic_ostream(const std::basic_ostream<char,std::char_traits<char>> &): attempting to reference a deleted function

如有任何帮助,我们将不胜感激。

您不能存储流的副本,它们不可复制。您必须改为存储 std::reference_wrapper 或指针。

class logger {
    std::vector<std::reference_wrapper<std::ostream>> streams;
public:
    void register_stream(std::ostream &stream) {
        streams.emplace_back(stream);
    }

    template <typename T>
    void operator<<(T const &value) {
        for (auto &stream : streams) { // note the '&'
            stream.get() << value;
        }
    }
};

或者如果您希望能够像 l << "log" << " this";:

那样链接您的电话
    template <typename T>
    logger & operator<<(T const &value) {
        for (auto &stream : streams) {
            stream.get() << value;
        }
        return *this;
    }

ostream 对底层 streambuf 进行格式化和写入。因此,当您多次使用 operator<< 时,它会不必要地多次格式化相同的输入。更优化的方法是格式化一次,然后使用未格式化输出函数 ostream::write 将格式化输出复制到多个底层 streams。

拥有 std::ostream 接口很方便,这样您就可以将它传递给需要 std::ostream 接口的现有函数。

您基本上需要自定义 streambuf 实现。从头开始写一个是很好的学习经验,但乏味且容易出错,因为 streambuf interface is somewhat hard to comprehend and implement correctly. Use The Boost Iostreams Library 相反。

工作示例:

#include <boost/iostreams/stream.hpp>
#include <algorithm>
#include <iostream>
#include <vector>

struct MultiSink {
    using char_type = char;

    struct category
        : boost::iostreams::sink_tag
        , boost::iostreams::flushable_tag
    {};

    std::vector<std::ostream*> sinks_;

    std::streamsize write(char const* s, std::streamsize n) {
        for(auto sink : sinks_)
            sink->write(s, n);
        return n;
    }

    bool flush() {
        for(auto sink : sinks_)
            sink->flush();
        return true;
    }

    void add_sink(std::ostream& s) {
        sinks_.push_back(&s);
    }

    void remove_sink(std::ostream& s) {
        sinks_.erase(std::remove(sinks_.begin(), sinks_.end(), &s), sinks_.end());
    }
};

int main() {
    MultiSink sink;
    boost::iostreams::stream<MultiSink> stream(sink);
    stream->add_sink(std::cout);
    stream->add_sink(std::cerr);

    stream << "Hello, world!" << std::endl;
}

请注意,代码假定已注册流的寿命超过多接收器。如果不是这种情况,您需要在销毁之前从多接收器注销流。

您有几个概念性问题需要解决:

  1. std::cout是一个全局对象,但是std::ostringstreamstd::ofstream类型。将它们讨论为可互换的输出是一个类别错误
  2. std::cout 是具有程序生命周期的全局对象,但您创建的任何 std::ofstream 实例 可能具有不同的生命周期。你需要一些方法来判断它的生命周期是否可以在你的记录器 之前 结束(这不是 cout 的问题,除非你的记录器也有程序生命周期),或者让记录器知道 负责您的流的生命周期。
  3. 拥有 std::vector<std::ostream> streams 是行不通的,因为:
    1. 它按值复制流,这是明确禁止的(参见删除的复制构造函数here
    2. 就算允许了也会因为object slicing.
    3. 而被破坏

有了这些,Maxim 的答案很好,但没有解决流的生命周期问题——如果这些都不是问题(你很乐意静态地保证每个注册的流都比记录器长寿),那么它就是一个很好的解决方案。

如果您确实需要一些额外的支持来管理对象的生命周期,您需要更详细的东西 - 例如。存储知道记录器是否拥有给定流的代理对象。

"If you do need some extra support to manage object lifetimes, you require something a bit more elaborate - eg. storing proxy objects that know whether or not the logger owns a given stream." – 没用

我当前项目中这个问题的当前解决方案看起来很像:

using ostream_deleter = std::function<void(std::ostream *)>;
using ostream_ptr = std::unique_ptr<std::ostream, ostream_deleter>;

这允许您存储一个新的流对象,具有所有权,例如

ostream_deleter d{std::default_delete<std::ostream>{}};
ostream_ptr fileStream{new std::ofstream{"/tmp/example.foo"}, std::move(d)};

请注意,出于异常安全的原因,您应该首先创建类型擦除的删除器。

它还允许您使用已知比您的记录器寿命更长的全局流:

ostream_ptr coutStream{&std::cout, [](std::ostream &) {}};

如果您想要更多自文档语法,还有一个 null_deleter in Boost

不幸的是,可能在内部 重定向 的流仍然存在问题。实际上,C++ 支持将任何流的输出(或输入)重定向到不同的流缓冲区,例如

std::ofstream logStream{"/tmp/my.log"};
const auto oldBuf = std::cout.rdbuf(logStream.rdbuf());
std::cout << "Hello World.\n"; // output redirected to /tmp/my.log
std::cout.rdbuf(oldBuf); // undo redirection

这里的问题是流缓冲区的生命周期与 logStream 相关,而不是 std::cout。这使任何生命周期管理 rsp 变得复杂。提出一个完全通用的解决方案,几乎是不可能的。当然,纪律和惯例应该仍然有很大帮助,而且不可否认,流重定向是一个相当晦涩且很少使用的功能。