Overload handling of std::endl?
我想定义一个类MyStream,以便:
1 2
| MyStream myStream;
myStream << 1 << 2 << 3 << std::endl << 5 << 6 << std::endl << 7 << 8 << std::endl; |
提供输出
1 2 3
| [blah]123
[blah]56
[blah]78 |
基本上,我想在前面插入一个" [blah]",然后在每个不终止的std::endl之后插入?
这里的困难不是逻辑管理,而是检测和超载std::endl的处理。 有没有一种优雅的方法可以做到这一点?
谢谢!
编辑:我不需要逻辑管理方面的建议。 我需要知道如何检测/重载std::endl的打印。
-
您只需要使用具有自己的sync()唯一版本的版本覆盖流缓冲区即可。
您需要做的是编写自己的流缓冲区:
刷新流缓冲区后,将输出前缀字符和流的内容。
由于std :: endl会导致以下结果,因此可以进行以下操作。
1)在流中添加" n"。
2)在流上调用flush()
2a)这会在流缓冲区上调用pubsync()。
2b)这将调用虚拟方法sync()
2c)重写此虚拟方法以执行所需的工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| #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:您抄袭我的建议时没有给予任何赞赏,而是留下了批评(是吗?),而且只进行了一半。您没有覆盖overflow,因此如果在调用sync之前putc(
)导致溢出,则代码将失败。
-
哦,stringbuf永远不会发生溢出。抱歉...这里的日出...困了。
-
是的...如果您应用令牌编辑,我明天将投票否决。
-
@potatoswatter:认为我抄袭了您有点自负。不涉及复制。您想出了一种可能性的描述。我想出了一个解决方案。而且我们完全独立地完成了它。 Guss什么:我在:-)之前做过这种事情
-
我希望通过读取std :: cin或程序结尾来触发刷新。你有什么建议吗?
-
@MacbethsEnigma:已经完成了。如果您从std :: cin读取,则std :: cout首先会自动刷新。当应用程序存在时,将清除所有缓冲区。
-
myStream <<"This does not print.";而std::cout <<"This does print.";我必须用myStream <<"This will print now" << std:flush;强制它,我希望复制end或std :: cin触发的cout行为。
-
来自Joseuutis C ++标准库,该方法有效:std::cin.tie(&myStream);是否可以将其合并到MyStream类中?我仍在尝试结束程序以触发刷新。
-
FWIW,我以SO问题的形式提出。 stackoverflow.com/questions/17532460/
-
问题,是否需要从MyStream的析构函数调用flush,还是std::ostream对您有用? 从en.cppreference.com/w/cpp/io/basic_ostream/%7Ebasic_ostream尚不清楚
-
@alfC:发现得很好。 添加了一个析构函数(到缓冲区)。
-
很好奇,尽管您会添加[virtual?] ~MyStream(){flush();}。 那会更简单吗? 和等价的?
MyStream类的重载运算符必须设置一个previous-printed-token-was-endl标志。
然后,如果打印了下一个对象,则可以将[blah]插入到它的前面。
std::endl是获取并返回对std::ostream的引用的函数。要检测到它已转移到流中,您必须在类型和这样的函数之间重载operator<<:
1 2 3 4 5 6 7 8 9 10 11
| MyStream& operator<<( std::ostream&(*f)(std::ostream&) )
{
std::cout << f;
if( f == std::endl )
{
_lastTokenWasEndl = true;
}
return *this;
} |
-
不幸的是,它不仅仅检测std :: endl -它检测到任何没有参数的操纵器。但是我猜想在<<()运算符中,您可以将f与std :: endl进行比较,如果它们是同一件事,则可以进行特殊处理。
-
在编辑帖子的过程中,我不确定与endl的比较是否可行,但我对其进行了测试,但它确实可行:-)
-
这与您永远无法从ostream派生的原因相同:格式化插入程序已定义为返回ostream&。即使您覆盖所有默认值,仍然会有用户定义的ostream &operator<<( ostream &, my_type const & )浮动。然后my_stream << my_type(5) << endl;调用operator<<( ostream&, manipulator ),而不是operator<<( MyStream&, manipulator )。
-
现有的代码关闭了所有其他操纵器-您需要以" else"逻辑调用它们:f(* this);
-
这应该起作用,除了GCC抱怨"假定强制类型转换为std :: basic_ostream >&(*)(std :: basic_ostream >&) f与std::endl的比较中,从"重载函数"中删除。 @Neil:这不会"关闭"任何操纵器;它们被传递到std::cout并在那里应用。
-
@Gabriel,您应该按照GCC的建议进行操作:强制转换if (f == (std::basic_ostream& (*)(std::basic_ostream&)) &std::endl) { ... }。
原则上同意尼尔。
您要更改缓冲区的行为,因为这是扩展iostream的唯一方法。 endl这样做:
1 2
| flush(__os.put(__os.widen('
'))); |
widen返回单个字符,因此您不能在其中放置字符串。 put调用不是虚拟函数的putc,仅偶尔挂接到overflow。您可以在flush处截取,它调用缓冲区的sync。您需要截取并更改所有换行符,因为它们是overflow或手动sync ed,并将它们转换为字符串。
设计覆盖缓冲区类很麻烦,因为basic_streambuf希望直接访问其缓冲区存储器。这样可以防止您轻松地将I / O请求传递给预先存在的basic_streambuf。您需要四处走动,并假设您知道流缓冲区类,然后从中派生该类。 (据我所知,不能保证cin和cout使用basic_filebuf。)然后,只需添加virtual overflow和sync。 (请参见§27.5.2.4.5/ 3和27.5.2.4.2 / 7。)执行替换可能需要更多空间,因此请务必提前分配空间。
- 要么 -
只需在您自己的命名空间中声明一个新的endl,或者更好地,一个根本不称为endl的操纵器!
-
您正在使事情复杂化。使用std :: stringbuf类进行缓冲。
-
@马丁:他问如何改变ostream的行为。我只能假设他想使用文件。无论如何,即使您使用stringbuf,您仍然处于派生,重载和替换的相同情况:它不会简化任何事情。另一方面,忘记整个流混乱并根据mystringstream.str()的结果执行搜索和替换将是非常合理的!
-
@Patatoswatter:请阅读我的解决方案。我认为30行(包括评论)相对来说是微不足道的。
-
@Martin:是的,它很简单,但是在复杂程度上,介于我的想法和"使用std :: stringbuf类"之间。
我使用函数指针。对于不习惯C的人来说,这听起来很可怕,但是在大多数情况下,它的效率要高得多。这是一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #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函数,但是我认为在大多数情况下不值得。希望对您有所帮助。
与其尝试修改std::endl的行为,不如创建一个过滤streambuf来完成这项工作。 James Kanze有一个示例,显示了如何在每条输出行的开头插入时间戳。它只需进行少量修改即可将其更改为您希望在每行上加上的任何前缀。
您不能更改std::endl-顾名思义,它是C ++标准库的一部分,并且其行为是固定的。当流接收到行尾时,您需要更改流本身的行为。就个人而言,我认为这样做不值得,但是如果您想涉足这一领域,我强烈建议您阅读《标准C ++ IOStreams和语言环境》一书。
-
好书。但是我不推荐初学者。但是,如果您想学习如何操作流类,则必须阅读。
-
@Martin我也不会向初学者推荐它,但是编写专业流并不是初学者的任务。
-
同意:<----- 15字符------> :-)