关于c ++:std :: endl的重载处理?

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的打印。


您需要做的是编写自己的流缓冲区:
刷新流缓冲区后,将输出前缀字符和流的内容。

由于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
>


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;
}


原则上同意尼尔。

您要更改缓冲区的行为,因为这是扩展iostream的唯一方法。 endl这样做:

1
2
flush(__os.put(__os.widen('
'
)));

widen返回单个字符,因此您不能在其中放置字符串。 put调用不是虚拟函数的putc,仅偶尔挂接到overflow。您可以在flush处截取,它调用缓冲区的sync。您需要截取并更改所有换行符,因为它们是overflow或手动sync ed,并将它们转换为字符串。

设计覆盖缓冲区类很麻烦,因为basic_streambuf希望直接访问其缓冲区存储器。这样可以防止您轻松地将I / O请求传递给预先存在的basic_streambuf。您需要四处走动,并假设您知道流缓冲区类,然后从中派生该类。 (据我所知,不能保证cincout使用basic_filebuf。)然后,只需添加virtual overflowsync。 (请参见§27.5.2.4.5/ 3和27.5.2.4.2 / 7。)执行替换可能需要更多空间,因此请务必提前分配空间。

- 要么 -

只需在您自己的命名空间中声明一个新的endl,或者更好地,一个根本不称为endl的操纵器!


我使用函数指针。对于不习惯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和语言环境》一书。