将流存储在变量中供以后使用
Store stream in a variable for later use
在我的代码中有一个地方,我必须将完全相同的 operator<<
流传递到 2 个不同的地方。一次 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
.
派生的 class
流缓冲区的简单实现如下所示:
#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());
}
};
除了对将输出转发到的流缓冲区列表进行一些微不足道的管理外,这个 class 覆盖了两个 virtual
函数:
overflow()
当流缓冲区的缓冲区(使用 setp()
设置)已满但另一个字符正在放入缓冲区时调用。这个函数所做的就是使用为这种情况保存的额外字符(如果函数没有用特殊值 std::char_traits<char>::eof()
调用)并调用 sync()
(见下文)。
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;
};
假设此流缓冲区和自定义流在 header "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;
}
将向多个流的发送打包成一个 class 的明显优势是您甚至不需要处理在多个地方转发字符串:您只需写入流并刷新(我有点against the use of std::endl
触发冲洗)。
在我的代码中有一个地方,我必须将完全相同的 operator<<
流传递到 2 个不同的地方。一次 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());
}
};
除了对将输出转发到的流缓冲区列表进行一些微不足道的管理外,这个 class 覆盖了两个 virtual
函数:
overflow()
当流缓冲区的缓冲区(使用setp()
设置)已满但另一个字符正在放入缓冲区时调用。这个函数所做的就是使用为这种情况保存的额外字符(如果函数没有用特殊值std::char_traits<char>::eof()
调用)并调用sync()
(见下文)。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;
};
假设此流缓冲区和自定义流在 header "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;
}
将向多个流的发送打包成一个 class 的明显优势是您甚至不需要处理在多个地方转发字符串:您只需写入流并刷新(我有点against the use of std::endl
触发冲洗)。