我想定义一个类MyStream
,以便:
MyStream myStream;
myStream << 1 << 2 << 3 << std::endl << 5 << 6 << std::endl << 7 << 8 << std::endl;
给出输出
[blah]123
[blah]56
[blah]78
基本上,我想在前面插入“[blah]”,然后在每次非终止 std::endl
之后插入?
这里的困难不是逻辑管理,而是检测和重载std::endl
的处理。是否有一种优雅的方式来做到这一点?
谢谢!
编辑:我不需要有关逻辑管理的建议。我需要知道如何检测/重载std::endl
的打印。
答案 0 :(得分:31)
您需要做的是编写自己的流缓冲区:
刷新流缓冲区时,输出前缀字符和流的内容。
以下是有效的,因为std :: endl会导致以下情况。
1)在流中添加'\ n'
2)在流上调用flush()
2a)这会在流缓冲区上调用pubsync()
2b)这称为虚拟方法sync()
2c)覆盖此虚拟方法以完成所需的工作。
#include <iostream>
#include <sstream>
class MyStream: public std::ostream
{
// Write a stream buffer that prefixes each line with Plop
class MyStreamBuf: public std::stringbuf
{
std::ostream& output;
public:
MyStreamBuf(std::ostream& str)
:output(str)
{}
~MyStreamBuf() {
if (pbase() != pptr()) {
putOutput();
}
}
// When we sync the stream with the output.
// 1) Output Plop then the buffer
// 2) Reset the buffer
// 3) flush the actual output stream we are using.
virtual int sync() {
putOutput();
return 0;
}
void putOutput() {
// Called by destructor.
// destructor can not call virtual methods.
output << "[blah]" << str();
str("");
output.flush();
}
};
// My Stream just uses a version of my special buffer
MyStreamBuf buffer;
public:
MyStream(std::ostream& str)
:std::ostream(&buffer)
,buffer(str)
{
}
};
int main()
{
MyStream myStream(std::cout);
myStream << 1 << 2 << 3 << std::endl << 5 << 6 << std::endl << 7 << 8 << std::endl;
}
> ./a.out
[blah]123
[blah]56
[blah]78
>
答案 1 :(得分:18)
MyStream
类的重载运算符必须设置previous-printed-token-was-endl标志。
然后,如果打印下一个对象,可以在其前面插入[blah]
。
std::endl
是一个获取并返回std::ostream
引用的函数。要检测它已移入您的流,您必须重载您的类型和此类函数之间的operator<<
:
MyStream& operator<<( std::ostream&(*f)(std::ostream&) )
{
std::cout << f;
if( f == std::endl )
{
_lastTokenWasEndl = true;
}
return *this;
}
答案 2 :(得分:1)
原则上同意尼尔。
您想要更改缓冲区的行为,因为这是扩展iostream的唯一方法。 endl
执行此操作:
flush(__os.put(__os.widen('\n')));
widen
会返回一个字符,因此您无法将字符串放在那里。 put
调用putc
,它不是虚函数,只偶尔挂钩overflow
。您可以在调用缓冲区flush
的{{1}}进行拦截。您需要截取并更改所有换行符,因为它们是sync
或手动overflow
并将它们转换为您的字符串。
设计覆盖缓冲区类很麻烦,因为sync
期望直接访问其缓冲区内存。这可以防止您轻松地将I / O请求传递给预先存在的basic_streambuf
。你需要走出困境,并假设你知道流缓冲类,并从中派生出来。 (basic_streambuf
和cin
无法保证使用cout
,我可以告诉您。)然后,只需添加basic_filebuf
和virtual overflow
。 (参见§27.5.2.4.5/ 3和27.5.2.4.2 / 7。)执行替换可能需要额外的空间,因此要小心提前分配。
只需在您自己的命名空间中声明一个新的sync
,或者更好的是,一个根本不称为endl
的操纵器!
答案 3 :(得分:1)
您应该创建一个过滤streambuf来完成这项工作,而不是尝试修改std::endl
的行为。 James Kanze有example显示如何在每个输出行的开头插入时间戳。它应该只需要很小的修改就可以将它改为你想要的每行前缀。
答案 4 :(得分:1)
我使用函数指针。对于那些不习惯C语言的人来说,这听起来很可怕,但在大多数情况下它会更有效率。这是一个例子:
#include <iostream>
class Foo
{
public:
Foo& operator<<(const char* str) { std::cout << str; return *this; }
// If your compiler allows it, you can omit the "fun" from *fun below. It'll make it an anonymous parameter, though...
Foo& operator<<(std::ostream& (*fun)(std::ostream&)) { std::cout << std::endl; }
} foo;
int main(int argc,char **argv)
{
foo << "This is a test!" << std::endl;
return 0;
}
如果你真的想要,你可以检查一下endl的地址,以确认你没有得到一些其他的void / void功能,但我认为在大多数情况下它是不值得的。我希望有所帮助。
答案 5 :(得分:1)
我有同样的问题,我认为Potatoswatter的第二个答案是有好处的:“只要在您自己的名称空间中声明一个新的endl,或者更好的是,一个根本不称为endl的操纵器!”
所以我找到了如何编写自定义操纵器的方法,这一点都不难:
#include <sstream>
#include <iostream>
class log_t : public std::ostringstream
{
public:
};
std::ostream& custom_endl(std::ostream& out)
{
log_t *log = dynamic_cast<log_t*>(&out);
if (log)
{
std::cout << "custom endl succeeded.\n";
}
out << std::endl;
return out;
}
std::ostream& custom_flush(std::ostream& out)
{
log_t *log = dynamic_cast<log_t*>(&out);
if (log)
{
std::cout << "custom flush succeeded.\n";
}
out << std::flush;
return out;
}
int main(int argc, char **argv)
{
log_t log;
log << "custom endl test" << custom_endl;
log << "custom flush test" << custom_flush;
std::cout << "Contents of log:\n" << log.str() << std::endl;
}
这是输出:
custom endl succeeded.
custom flush succeeded.
Contents of log:
custom endl test
custom flush test
在这里,我创建了两个自定义操纵器,一个处理endl
,另一个处理flush
。您可以将所需的任何处理添加到这两个函数中,因为您有一个指向log_t
对象的指针。
答案 6 :(得分:0)
您无法更改std::endl
- 因为它的名称表明它是C ++标准库的一部分,其行为是固定的。当接收到行尾时,您需要更改流本身的行为。就个人而言,我不认为这值得付出努力,但如果你想冒险进入这个领域,我强烈建议你阅读这本书Standard C++ IOStreams & Locales。