Record all cout output in C++

2016-03-27 17:41:32 Coding C++

我去年做UROP的时候,用C++写了一个控制台程序。后来希望把整个过程自动化,也就需要导出程序的所有输出。然而这些输出本身是在cout里的,我也不想弄得太复杂(比如再写个程序wrap在外面什么的)。同时我希望这个过程是透明的——我不需要修改那些用了cout<<的地方。

于是先写一个用于把输入一个streambuf的字符分配到两个streambuf中的dist_streambuf:

/*
dist_streambuf.h
Author: logchan
Created: 2015-07-29
Header file for class dist_streambuf.
A dist_streambuf object is used to distribute the output operations to two streambuf objects (primary and secondary).
It can be used in some tricks, such as recording cout.
*/
#ifndef _LOGCHAN_DIST_STREAMBUF_H
#define _LOGCHAN_DIST_STREAMBUF_H
#include <streambuf>
#include <locale>
#include <ios>

using std::streambuf;
using std::streamsize;
using std::streampos;
using std::streamoff;
using std::locale;
using std::ios_base;

class dist_streambuf : public streambuf
{
public:
    dist_streambuf(streambuf* primary, streambuf* secondary);
    streambuf* primary_buf();
    streambuf* primary_buf(streambuf* newbuf);
    streambuf* secondary_buf();
    streambuf* secondary_buf(streambuf* newbuf);

protected:
    // Locales
    void imbue(const locale& loc);
    // Buffer management and positioning
    streambuf* setbuf(char* s, streamsize n);
    streampos seekoff(streamoff off, ios_base::seekdir way, ios_base::openmode which = ios_base::in | ios_base::out);
    streampos seekpos(streampos sp, ios_base::openmode which = ios_base::in | ios_base::out);
    int sync();
    // Input functions
    // Not implemented. It does not make sense to "distribute input to" two streambufs.
    // Output functions
    streamsize xsputn(const char* s, streamsize n);
    int overflow(int c = EOF);

private:
    streambuf* buf1;
    streambuf* buf2;
};

#endif //_LOGCHAN_DIST_STREAMBUF_H

以及实现的cpp文件:

#include "dist_streambuf.h"
#include <streambuf>
#include <stdexcept>
using namespace std;

dist_streambuf::dist_streambuf(streambuf * primary, streambuf * secondary)
{
    if (primary == nullptr)
    {
        throw invalid_argument("null primary buf is not allowed.");
    }

    buf1 = primary;
    buf2 = secondary;
}

streambuf * dist_streambuf::primary_buf()
{
    return buf1;
}

streambuf * dist_streambuf::primary_buf(streambuf * newbuf)
{
    if (newbuf == nullptr)
    {
        throw invalid_argument("null primary buf is not allowed.");
    }

    streambuf* prevbuf = buf1;
    buf1 = newbuf;
    return prevbuf;
}

streambuf * dist_streambuf::secondary_buf()
{
    return buf2;
}

streambuf * dist_streambuf::secondary_buf(streambuf * newbuf)
{
    streambuf* prevbuf = buf2;
    buf2 = newbuf;
    return prevbuf;
}
streamsize dist_streambuf::xsputn(const char * s, streamsize n)
{
    streamsize r = buf1->sputn(s, n);
    if (buf2 != nullptr)
    {
        buf2->sputn(s, n);
    }

    return r;
}

int dist_streambuf::overflow(int c)
{
    int r = buf1->sputc(c);
    if (buf2 != nullptr)
    {
        buf2->sputc(c);
    }

    return r;
}

void dist_streambuf::imbue(const locale & loc)
{
    buf1->pubimbue(loc);
    if (buf2 != nullptr)
    {
        buf2->pubimbue(loc);
    }
}

streambuf * dist_streambuf::setbuf(char * s, streamsize n)
{
    streambuf* r = buf1->pubsetbuf(s, n);
    if (buf2 != nullptr)
    {
        buf2->pubsetbuf(s, n);
    }

    return r;
}

streampos dist_streambuf::seekoff(streamoff off, ios_base::seekdir way, ios_base::openmode which)
{
    streampos r = buf1->pubseekoff(off, way, which);
    if (buf2 != nullptr)
    {
        buf2->pubseekoff(off, way, which);
    }

    return r;
}

streampos dist_streambuf::seekpos(streampos sp, ios_base::openmode which)
{
    streampos r = buf1->pubseekpos(sp, which);
    if (buf2 != nullptr)
    {
        buf2->pubseekpos(sp, which);
    }

    return r;
}

int dist_streambuf::sync()
{
    int r = buf1->pubsync();
    if (buf2 != nullptr)
    {
        buf2->pubsync();
    }

    return r;
}

dist_streambuf允许buf2为nullptr。使用时,通过cout本身的buffer和一个sstream的buffer构造一个dist_streambuf并用其替换cout原来的buffer就可以了:

// initialize the logging of cout
auto preserved_cout_buf = cout.rdbuf();
auto logstream_buf = logstream.rdbuf();
dist_buf = new dist_streambuf(preserved_cout_buf, logstream_buf);
cout.rdbuf(dist_buf);

如果希望临时关闭分配,既可以把preserved_cout_buf还给cout,也可以用dist_buf->secondary_buf(nullptr);来使logstream_buf不再接受到新的字符。当然,如果想分配到更多的buffer,也可以把一个dist_streambuf作为另一个的primary或者secondary……这么一说,构造器没有检查传入的是不是等于this似乎有点危险……失策

啊,多么好用的东西。