[英]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_stream
、 std::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::cout
和std::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::endl
和std::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.