将流存储在变量中供以后使用

Store stream in a variable for later use

本文关键字:存储 变量      更新时间:2023-10-16

在我的代码中,我有一个地方,我必须将完全相同的operator<<流传递到两个不同的地方。一次到ofstream,一次到cout

m_logFileStream << "[" << now->tm_hour << ":" << now->tm_min << ":" << now->tm_sec << "]"
                << "[" << logLevelsStrings[(int)logline.logLevel] << "] "
                << logline.logString << endl;
        if(m_verbose)
        {
            cout << "[" << now->tm_hour << ":" << now->tm_min << ":" << now->tm_sec << "]"
                            << "[" << logLevelsStrings[(int)logline.logLevel] << "] "
                            << logline.logString << endl;
        }

m_logFileStream是一个ofstream.如果我想改变模式,我需要在两个地方进行。将它顶部存储在这样的变量中会更方便:

stringstream ss;
        ss      << "[" << now->tm_hour << ":" << now->tm_min << ":" << now->tm_sec << "]"
                << "[" << logLevelsStrings[(int)logline.logLevel] << "] "
                << logline.logString << endl;
        m_logFileStream << ss;
        if(m_verbose)
        {
            cout << ss;
        } 

但是由于某种原因,我得到的不是正确的输出,而是随机十六进制数。我在这里做错了什么?

编辑

cout << ss.str();工作,但m_logFileStream << ss.str();不会将任何内容保存到为其创建m_logFileStream的文件中。

代码

的直接问题已经讨论过:插入流会导致转换运算符被触发void const*并导致打印指针值(很可能是流的地址(。解决方法是改用ss.str()ss.rdbuf(),后可能跟一个std::flush。请注意,ss.str()每次调用时都会创建一个std::string。如果流包含大量可能不太理想的数据。插入流时,ss.rdbuf()也应该可以工作,并且可以绕过创建额外的流。但是,在使用它两次之间,需要设置插入的流以再次迭代序列,例如,通过查找到开始。

到目前为止,用于修补原始设计。不过,我会建议对整个问题采用不同的设计:与其先创建一个字符串,然后将其插入两次到两个不同的流中,不如创建一个在内部转发到一个或多个流的流。启用创建新流的魔术称为流缓冲区,即从std::streambuf派生的类。

流缓冲区的简单实现可能如下所示:

#include <streambuf>
#include <algorithm>
#include <ostream>
#include <vector>
class teebuf
    : public std::streambuf
{
    char                         buffer[1024];
    std::vector<std::streambuf*> sbufs;
    int overflow(int c) {
        typedef std::streambuf::traits_type traits;
        if (!traits::eq_int_type(traits::eof(), c)) {
            *this->pptr() = traits::to_char_type(c);
            this->pbump(1);
        }
        return this->sync() == 0? traits::not_eof(c): traits::eof();
    }
    int sync() {
        bool rc(false);
        if (this->pbase() != this->pptr()) {
            std::for_each(sbufs.begin(), sbufs.end(),
                [&](std::streambuf* sb){
                    sb->sputn(this->pbase(), this->pptr() - this->pbase());
                    sb->pubsync() != -1 || (rc = false);
                });
            this->setp(buffer, buffer + sizeof(buffer) - 1);
        }
        return rc? -1: 0;
    }
public:
    teebuf() { this->setp(buffer, buffer + sizeof(buffer) - 1); }
    void add(std::ostream& out){ sbufs.push_back(out.rdbuf()); }
    void remove(std::ostream& out){
        sbufs.erase(std::remove(sbufs.begin(), sbufs.end(), out.rdbuf()),
                    sbufs.end());
    }
};

除了对要将输出转发到的流缓冲区列表进行一些微不足道的管理之外,此类还重写了两个virtual函数:

  1. 当流缓冲区的缓冲区(使用 setp() 设置(已满但另一个字符被放入缓冲区时,将调用 overflow()。此函数所做的只是使用在这种情况下保存的额外字符(如果未使用特殊值 std::char_traits<char>::eof() 调用函数(并调用sync()(见下文(。
  2. 当需要刷新当前缓冲区时调用sync(),例如,由于用户要求使用 std::flush 刷新流或缓冲区已满。

若要实际使用此流缓冲区,请创建一个std::ostream并使用指向此std::streambuf的指针对其进行初始化。这类似于std::ofstream对其std::filebuf所做的。为了使合适的流的创建更容易一些,将其打包是有意义的:

class oteestream
    : private virtual teebuf
    , public std::ostream {
public:
    oteestream()
        : teebuf()
        , std::ostream(this) {
        this->init(this);
    }
    using teebuf::add;
    using teebuf::remove;
};

假设此流缓冲区和自定义流在标头中声明"teestream.h"它的使用变得相当简单:

#include "teestream.h"
#include <fstream>
#include <iostream>
int main()
{
    std::ofstream fout("tee.txt");
    oteestream    tee;
    tee.add(fout);
    tee.add(std::cout);
    tee << "hello, world!n" << std::flush;
    tee.remove(std::cout);
    tee << "goodbye, world!n" << std::flush;
}
将发送到多个流

的明显优势是,您甚至不需要处理在多个位置转发字符串:您只需写入流并刷新(我有点反对使用 std::endl 来触发刷新(。