33

我想定义一个类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.

4

7 回答 7

34

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

以下工作,因为std::endl导致以下。

  1. 添加'\n'到流中。

  2. flush()在流上调用

  3. 这会调用pubsync()流缓冲区。

    1. 这调用了虚方法sync()
    2. 覆盖此虚拟方法以完成您想要的工作。
#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
>
于 2010-02-06T11:38:14.330 回答
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;
}
于 2010-02-06T10:20:01.627 回答
2

原则上同意尼尔。

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

flush(__os.put(__os.widen('\n')));

widen返回一个字符,所以你不能把你的字符串放在那里。put调用putc它不是一个虚函数,只是偶尔挂钩到overflow. 您可以拦截 at flush,它调用缓冲区的sync. 您需要在overflow编辑或手动sync编辑时拦截并更改所有换行符并将它们转换为您的字符串。

设计一个覆盖缓冲区类很麻烦,因为basic_streambuf需要直接访问它的缓冲存储器。这可以防止您轻松地将 I/O 请求传递给预先存在的basic_streambuf. 您需要冒险并假设您知道流缓冲区类,并从中派生。(据我所知,不保证使用。)然后,cin只需cout添加and 。(请参阅 §27.5.2.4.5/3 和 27.5.2.4.2/7。)执行替换可能需要额外的空间,因此请小心提前分配。basic_filebufvirtual overflowsync

- 或者 -

只需endl在您自己的命名空间中声明一个新的,或者更好的是,一个根本不被调用的操纵器endl

于 2010-02-06T11:30:02.573 回答
1

与其尝试修改 的行为std::endl,不如创建一个过滤流缓冲区来完成这项工作。James Kanze 有一个示例,展示了如何在每个输出行的开头插入时间戳。只需稍作修改即可将其更改为每行所需的任何前缀。

于 2010-02-06T16:36:06.183 回答
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 的地址以确认你没有得到一些 OTHER void/void 函数,但我认为在大多数情况下它不值得。我希望这会有所帮助。

于 2012-04-07T23:54:13.693 回答
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对象的指针。

于 2020-10-27T16:36:54.630 回答
0

你不能改变std::endl- 正如它的名字所暗示的那样,它是 C++ 标准库的一部分并且它的行为是固定的。当它收到 end of line 时,您需要更改流本身的行为。就个人而言,我不认为这值得付出努力,但如果您想涉足这个领域,我强烈建议您阅读《标准 C++ IOStreams & Locales 》一书。

于 2010-02-06T10:27:13.017 回答