繁体   English   中英

当 std::ostream object 被 std::flush 时,如何插入换行符?

[英]How can I inject a newline when a std::ostream object is std::flush'ed?

我如何最低限度地包装std::ofstream以便对sync ( std::flush ) 的任何调用都变成对std::endl的调用。

(以下所有内容都是对“你为什么要那样做?”问题的回答,与上述问题无关)


我有一个包含自定义std::ostream实现(环绕 zmq)的应用程序,我们称它为zmq_stream

这个zmq_stream也在内部实现了一个自定义的streambuffer ,并且工作得很好。

这种设计的动机之一是在我的单元测试中,我可以简单地将单元测试中的zmq_stream替换为常规的旧std::ostringstream并测试结果,完全隐藏 .network 层。

应用程序本身的功能之一是通过命令行标志将应用程序 output 重定向到文件或标准输出。

再一次,在启动时使用基本开关,我可以将zmq_streamstd::ofstream或普通的旧std::cout传递到我的主循环。

这是完成这样的事情:

std::ostream* out;
switch(cond)
{
  case 1:
    out = &std::cout;
    break;
  case 2:
    out = new ofstream("file");
    break;
  case 3:
    out = new zmq_stream(method, endpoints);
    break;
}

main_loop(out);

zmq_stream的用法如下:


zmq_stream out(method, endpoints);

out << "do something " << 42 << " or other";
out << "and another" << std::flush; // <- full constructed buffer is sent over the network

注意:根据设计,我在刷新到 .network 时使用std::flush而不是std::endl 我不希望 append 换行到所有 my.network 传输。 因此,应用程序的 all.network 输出使用std::flush而不是std::endl并且更改它不是正确的答案。

问题:出于测试目的,一切都按预期工作,对于std::coutstd::ofstream选项,我希望能够在调用std::flush插入换行符,以便 my.network 传输可以在stdout上分隔成换行符。 这将允许我将它们 pipe 添加到标准的 posix 工具套件中......

没有这种注入,就没有办法确定(除了计时)网络边界在哪里。

我在这里寻找尽可能少的覆盖,这样我就不必写出一堆新类。 理想情况下,重写sync()虚方法以便它调用overflow('\n'); 然后调用 base sync()

然而,事实证明这比我预期的更具挑战性,所以我想知道我是否做错了。

我如何包装文件 stream.

您可以编写一个<<重载来拦截std::flush并添加\n 我只是概述了这个想法,没有生产就绪代码。

#include <iostream>

struct foo {
    std::ostream& out = std::cout;

    template <class T> 
    foo& operator<<(const T& t) {
        out << t;
        return *this;
    }

    foo& operator<<(std::ostream& (*f)(std::ostream&)) {
        if (f == &std::flush<std::ostream::char_type,std::ostream::traits_type>) { out << "\n"; }
        out << f;
        return *this;
    }
};


int main() {
    foo f;
    f << "a"  << std::endl << "b" << std::flush << "c";
}

在 output 中,您可以看到现在std::endlstd::flush都添加了一个\n std::endl因为它无论如何都这样做了,而std::flush因为包装器添加了它。 现场演示

a
b
c

通常参考std function 应该小心。 仅当它们是显式可寻址函数时才允许。 实际上我没有发现提到过,但是将它们作为 function 指针传递是 io-manipulators 应该如何使用。

我写的初始解决方案不起作用,因为它是一个编译时解决方案,如果不重新实现 inheritance 就无法打开它(这样main_loop function 得到一个基数 class 作为参数)。

相反,这是原始问题的答案:

#include <iostream>
#include <fstream>
#include <iomanip>

typedef std::ostringstream zmq_stream;

template <class T>
class newline_injector_streambuf: public std::basic_streambuf<T> {
public:
    using int_type = typename std::basic_streambuf<T>::int_type;
    newline_injector_streambuf(std::basic_streambuf<T>& dest) : sink(dest) {}

protected:

    virtual int_type sync() override { overflow('\n'); return sink.pubsync(); }
    virtual int_type overflow(int_type c) override { return sink.sputc(c); }

    std::basic_streambuf<T>& sink;
};

template <class T>
struct newline_injector_stream : public std::basic_ostream<typename T::char_type> {
    newline_injector_streambuf<typename T::char_type> buf;
    newline_injector_stream(T* file) : buf(*file->rdbuf())
    {
        std::basic_ostream<typename T::char_type>::rdbuf(&buf);
    }
};

void test(std::ostream& out)
{
    out << std::setfill('x') << std::setw(10) << "" << std::flush;
    out << "Hello, world!" << std::flush << "asdf" << std::endl;
}

int main() {

    newline_injector_stream out1(&std::cout);
    newline_injector_stream out2(new std::ofstream("test.output", std::ios::out | std::ios::ate));

    test(std::cout);
    test(out1);
    test(out2);

    return 0;
}



这是由于 @463035818-is-not-a-number 的初始答案而设计的解决方案:

    struct zmq_stream;
    template<typename T>
    struct output_device {
        T& output;

        output_device(T& backing_stream) : output(backing_stream){}

        template <typename U>
        output_device& operator<<(const U& u)
        {
            output << u;
            return *this;
        }

        output_device& operator<<(std::ostream& (*f)(std::ostream&)) {

          if constexpr (!std::is_same<T, zmq_stream>::value)
            if (f == &std::flush<std::ostream::char_type,std::ostream::traits_type>)
                output << "\n";
            output << f;
            return *this;
        }
    };

// Usage:

int main()
{
   /* prelude code */
   auto out1 = output_device(std::cout);
   auto out2 = output_device(std::ofstream("filename"));
   auto out3 = output_device(zmq_stream(method, endpoints));

   out1 << "foo" << std::flush << "bar"; // results in foo\nbar
   out2 << "foo" << std::flush << "bar"; // results in foo\nbar
   out3 << "foo" << std::flush << "bar"; // results in foobar

}

template <class T>
class separator: public std::basic_streambuf<T> {
public:
    using int_type = typename std::basic_streambuf<T>::int_type;
    using char_type = typename std::basic_streambuf<T>::char_type;
    using traits_type = typename std::basic_streambuf<T>::traits_type;

    separator(std::basic_streambuf<T> *dest) : sink(dest) {}
protected:

    virtual int_type sync() override {
        if (nullptr == sink)
            return 1;
        //std::basic_streambuf<T>::overflow('\n');
        overflow('\n');
        return sink->pubsync();
    }

    virtual int_type overflow(int_type c) override {
        if (sink == nullptr)
            return 1;
        return sink->sputc(c);
    }

    std::basic_streambuf<T> *sink = nullptr;
};

template <class T> struct newline_injector_stream : public std::basic_ostream<T> {
    separator<T> buf;
public:
    newline_injector_stream(std::basic_ostream<T> &file) : buf(file.rdbuf())
    {
        std::basic_ostream<T>::rdbuf(&buf);
    }
};

main_loop function 签名现在从采用std::ostream*类型变为output_device&类型。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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