假设我想创建一个在语句末尾执行操作的流,以便
myStream << "Hello, " << "World!";
会一次打印“Hello,World!\ n” 。不是“Hello,\ nWorld!\ n”而不是“Hello,World!”,而是“Hello,World \ n”,好像;
会触发附加\n
并刷新缓冲区。
它的基本原理是一个写入stdout
和日志文件的流类,日志文件条目具有某些前缀和后缀。
例如,如果我的目标是HTML,我会想要这段代码:
myStream << "Hello, " << "World!";
myStream << "Good bye, " << "cruel World!";
像这样打印:
<p>Hello, World!</p>
<p>Good bye, cruel World!</p>
和不喜欢这样:
<p>Hello, </p><p>World!</p>
<p>Good bye, </p><p>cruel World!</p>
现在,如果我实现LogStream
类似这样的话:
LogStream & LogStream::operator<<( const std::string & text );
我无法区分陈述中间的<<
与陈述开头/结尾的LogStream
。
如果我实现LogStream LogStream::operator<<( const std::string & text );
类似这样的话:
endl
并尝试按下析构函数中的输入,我会在块结束时立即获得多个析构函数。
最后,我可以在每个语句的末尾实现这个我的要求{{1}},但我宁愿不打扰调用者有必要这样做。
因此问题是:如何以呼叫者透明的方式实现这样的流?
答案 0 :(得分:2)
我为自定义日志系统实现了一次这样的事情。我创建了一个缓冲输入的类,然后它的析构函数将缓冲区刷新到我的日志文件中。
例如:
#include <iostream>
#include <sstream>
#include <string>
class LogStream
{
private:
std::stringstream m_ss;
void flush()
{
const std::string &s = m_ss.str();
if (s.length() > 0)
{
std::cout << s << std::endl;
logfile << "<p>" << s << "</p>" << std::endl;
}
}
public:
LogStream() {}
~LogStream()
{
flush();
}
template <typename T>
LogStream& operator<<(const T &t)
{
m_ss << t;
return *this;
}
template <typename T>
LogStream& operator<<( std::ostream& (*fp)(std::ostream&) )
{
// TODO: if fp is std::endl, write to log and reset m_ss
fp(m_ss);
return *this;
}
void WriteToLogAndReset()
{
flush();
m_ss.str(std::string());
m_ss.clear();
}
};
对于在最终;
上刷新的单个语句,每条消息都将使用该类的新实例:
LogStream() << "Hello, " << "World!";
LogStream() << "Good bye, " << "cruel World!";
要允许多个语句写入单个消息,请创建该对象,并且在最后一个语句完成之前不要让它超出范围:
{
LogStream myStream;
myStream << "Hello, ";
myStream << "World!";
}
{
LogStream myStream;
myStream << "Good bye, ";
myStream << "cruel World!";
}
要为多条消息重用现有实例,请告诉它在每条消息之间刷新和重置:
{
LogStream myStream;
myStream << "Hello, " << "World!";
myStream.WriteToLogAndReset();
myStream << "Good bye, " << "cruel World!";
}
我喜欢这种方法,因为它可以让调用者更灵活地决定何时可以将每条消息写入日志文件。例如,我使用它将多个值发送到单个日志消息,其中这些值是从决策代码分支获得的。这样,我可以传输一些值,做出一些决定,传输更多的值等,然后将完成的消息发送到日志。
答案 1 :(得分:0)
该行:
myStream << "Hello, " << "World!";
实际上是多个陈述。它相当于:
ostream& result = myStream << "Hello, ";
result << "World!";
通过返回一个未命名的临时对象(在完整表达式的末尾被销毁), 有些技巧可以做你想要的。 Here is some example code
答案 2 :(得分:0)
我有一些代码,我还没有做任何有建设性的事情,我认为这样做是在做你所要求的。
它的工作原理是使用代理类(log_buffer
)在std::stringstream
对象中构建字符串。在表达式结束时,log_buffer
代理对象调用主log_writer
对象来处理std::stringstream
中包含的整行。
class log_writer
{
// ultimate destination
std::ostream* sink = nullptr;
// proxy class to do the << << << chaining
struct log_buffer
{
log_writer* lw;
std::stringstream ss;
void swap(log_buffer& lb)
{
if(&lb !=this)
{
std::swap(lw, lb.lw);
std::swap(ss, lb.ss);
}
}
log_buffer(log_writer& lw): lw(&lw) {}
log_buffer(log_buffer&& lb): lw(lb.lw), ss(std::move(lb.ss)) { lb.lw = nullptr; }
log_buffer(log_buffer const&) = delete;
log_buffer& operator=(log_buffer&& lb)
{
swap(lb);
return *this;
}
log_buffer& operator=(log_buffer const&) = delete;
// update the log_writer after the last call to << << <<
~log_buffer() { if(lw) lw->add_line(ss); }
template<typename DataType>
log_buffer operator<<(DataType const& t)
{
ss << t;
return std::move(*this);
}
};
void swap(log_writer& lw)
{
if(&lw != this)
{
std::swap(sink, lw.sink);
}
}
public:
log_writer(std::ostream& sink): sink(&sink) {}
log_writer(log_writer&& lw): sink(lw.sink) { lw.sink = nullptr; }
log_writer(log_writer const&) = delete;
log_writer& operator=(log_writer&& lw)
{
swap(lw);
return *this;
}
log_writer& operator=(log_writer const&) = delete;
// output the final line
void add_line(std::stringstream& ss)
{
// Do any special line formatting here
if(sink) (*sink) << ss.str() << std::endl;
}
template<typename DataType>
struct log_buffer operator<<(DataType const& data)
{
return std::move(log_buffer(*this) << data);
}
};
int main()
{
std::ofstream ofs("test.log");
log_writer lw1(ofs);
log_writer lw2(std::cout);
lw1 << "lw1 " << 2.93 << " A";
lw2 << "lw2 " << 3.14 << " B";
std::swap(lw1, lw2);
lw1 << "lw1 " << 2.93 << " C";
lw2 << "lw2 " << 3.14 << " D";
}