如何从std :: ostream派生实现自定义的输出流类

本文为博主原创文章,未经博主允许不得转载。(合作洽谈请联系QQ:1010316426)

缘起:

笔者的项目产品上一般使用C风格的库记录运行日志,日志库的接口是如printf样式。近日,需要引入其他模块(不同途径获取)的代码,但是发现这个模块记录日志是使用C++的流式风格。因代码里巨大,手动修改费时费力(不符合程序员做事风格),于是笔者想到是否可以将流式的log只通过替换其日志宏 从而实现在不修改代码的前提下将流式转为printf式,从而完成log部分的整合。

//printf风格
void write_log(const char* sFormat, ...);
//流式风格
#define write_log /*写日志的宏*/
write_log<<"my log info"<<std::endl;

方案:

笔者想定义从std :: ostream公开派生出的MyOStream,从而实现能够在MyOStream上使用operator <<和write()以及put()并使用我的类的扩展功能。
通过阅读glog的代码,发现实现起来实际上并不那么难。基本上只是对std::ostreamstd::streambuf对象进行子类化,并将自身构造为缓冲区。将为发送到流的每个字符调用std :: streambuf中的虚拟overflow()。
一个简单的示例可以如下来做:

struct MyOstream : std::ostream, std::streambuf
{
    MyOstream() : std::ostream(this) {}
    int overflow(int c)
    {
        foo(c);
        return 0;
    }
    void foo(char c)
    {
        std::cout.put(c);
    }
};

void test_my_ostream2()
{
    MyOstream b;
    b << "Look a number:0x" << std::hex << 39 << std::endl;
}

实现:

特别的,glog中的实现比这个要复杂不少,但是原理还是如此。下面是笔者从glog中剥离抽取出的相关核心类代码:

const size_t kMaxLogMessageLen = 30000;

const char* const_basename(const char* filepath) {
    const char* base = strrchr(filepath, '/');
    if (!base)
        base = strrchr(filepath, '\\');
    return base ? (base + 1) : filepath;
}

class LogStreamBuf : public std::streambuf {
public:
    // REQUIREMENTS: "len" must be >= 2 to account for the '\n' and '\0'.
    LogStreamBuf(char* buf, int len) {
        setp(buf, buf + len - 2);
    }

    // This effectively ignores overflow.
    virtual int_type overflow(int_type ch) {
        return ch;
    }

    // Legacy public ostrstream method.
    size_t pcount() const { return pptr() - pbase(); }
    char* pbase() const { return std::streambuf::pbase(); }
};

class LogStream : public std::ostream {
public:
    LogStream(char* buf, int len, int ctr)
        : std::ostream(NULL),
        streambuf_(buf, len),
        ctr_(ctr),
        self_(this) {
        rdbuf(&streambuf_);
    }

    int ctr() const { return ctr_; }
    void set_ctr(int ctr) { ctr_ = ctr; }
    LogStream* self() const { return self_; }

    // Legacy std::streambuf methods.
    size_t pcount() const { return streambuf_.pcount(); }
    char* pbase() const { return streambuf_.pbase(); }
    char* str() const { return pbase(); }

private:
    LogStream(const LogStream&);
    LogStream& operator=(const LogStream&);
    LogStreamBuf streambuf_;
    int ctr_;  // Counter hack (for the LOG_EVERY_X() macro)
    LogStream* self_;  // Consistency check hack
};

struct LogMessageData {
    LogMessageData()
        : stream_(message_text_, kMaxLogMessageLen, 0) {
    };

    char message_text_[kMaxLogMessageLen + 1];
    LogStream stream_;
    char severity_;      // What level is this LogMessage logged at?
    int line_;                 // line number where logging call is.
    size_t num_chars_to_log_;     // # of chars of msg to send to log
    const char* basename_;        // basename of file that called LOG
    const char* fullname_;        // fullname of file that called LOG

private:
    LogMessageData(const LogMessageData&);
    void operator=(const LogMessageData&);
};

class LogMessage {
public:
    LogMessage(const char* file, int line, int severity)
    {
        //Init(file, line, severity, &LogMessage::SendToLog);
        data_ = new LogMessageData;

        data_->basename_ = const_basename(file);
        data_->fullname_ = file;
        data_->line_ = line;
        data_->severity_ = severity;
    }
    ~LogMessage() {
        data_->num_chars_to_log_ = data_->stream_.pcount();
        data_->message_text_[data_->num_chars_to_log_+1] = '\0';
        std::cout << "hahaha>>" << data_->basename_ << ":" << data_->line_ << " " << data_->message_text_ << "\r\n";
		delete data_;
    }
    std::ostream& stream() {
        return data_->stream_;
    }
    LogMessageData* data_;
private:
    LogMessage(const LogMessage&);
    void operator=(const LogMessage&);
};

void test_my_ostream()
{
    LogMessage(__FILE__, __LINE__, 0).stream() << " i am tester! ";
}

1.在 LogMessage 的构造函数中创建LogMessageData对象;
2.使用LogMessage::stream(),利用operator<< 可以将流式内容缓冲到LogMessageData::message_text_ 之中;
3.在LogMessage析构函数中,笔者使用std::cout将缓冲区打印出来。读者也可以尝试换成其他的方式处理,比如调用fwrite/写log等等。例如,glog中就使用SendToLog/SendToSinkAndLog/SendToSink/SaveOrSendToLog/WriteToStringAndLog等很多种函数。

你可能感兴趣的:(C++语言,软件架构,数据结构与算法,ostream,streambuf,glog,日志库风格)