[英]C++ Stream: Insert string after newline
原始问题是:换行发生后,如何将某些内容自动插入流中。 仅当事后将(手动)插入流中时,才应进行插入。
以下是更详细的说明。
为了练习,我正在编写自己的记录器类。 此片段提供了基本的日志记录功能。
class Logger {
public:
static std::ostream & log( LogLevels::LogLevel level );
};
// Use it:
Logger::log( LogLevels::WARNING ) << "Something serious happened!" << std::endl;
该示例打印出类似
[ WARNING: 0 ] Something serious happened!
我想扩展此功能,以便在将换行符插入流中之后,所有调试消息均以日志记录头的宽度缩进,直到再次调用Logger::log
。 最好通过显示示例来解释:
// Example (1)
Logger::log( LogLevels::WARNING ) << "Something happened!"
<< std::endl
<< "Some More information: "
<< 42
<< " (Still on the same line)"
<< std::endl;
Logger::log( LogLevels::INFO ) << "Merely a status code"
<< std::endl
<< "Which reads: "
<< 21
<< " (Also on the same line)"
<< std::endl;
// Example (2)
std::ostream & os = Logger::log( LogLevels::WARNING );
os << "First line"
<< std::endl;
os << "Second line"
<< std::endl;
// Example (3)
// [...]
// Some code is executed
Logger::log( LogLevels::WARNING ) << "A new error"
<< std::endl
<< "The code strikes back"
<< std::endl
<< "The return of the bugs"
<< std::endl ;
这将产生:
[ WARNING: 0 ] Something hapened! Some More information: 42 (Still on the same line) [ INFO: 1 ] Merely a status code Which reads: 21 (Also on the same line) [ WARNING: 2 ] First line Second line // [...] [ WARNING: 99998 ] A new error The code strikes back The return of the bugs
这种行为可以实现吗?如果可以,如何实现?
只需在ostream和最终目标streambuf之间插入一个过滤streambuf。 如下所示的方法可以解决问题:
class HeaderInserter : public std::streambuf
{
std::streambuf* myDest;
bool myIsAtStartOfLine;
protected:
int overflow( int ch ) override
{
int retval = 0;
if ( ch != traits_type::eof() ) {
if ( myIsAtStartOfLine ) {
std::string header = getHeader();
myDest->sputn( header.data(), header.size() );
}
retval = myDest->sputc( ch );
myIsAtStartOfLine = ch == '\n';
}
return retval;
}
public:
HeaderInserter( std::streambuf* dest )
: myDest( dest )
, myIsAtStartOfLine( true )
{
}
};
创建其中之一,并带有指向最终目的地的指针( std::cerr.rdbuf()
或您打开的std::filebuf
),然后使用指向该目标的std::ostream
。
我在自己的记录器类中使用它; 我添加了其他功能来启动和完成新的记录器记录:开始记录后的第一个输出将输出时间戳,以及我向下传递的__FILE__
和__LINE__
; 以下标题只是缩进的任意数量的空格。 并且finish函数还将确保记录以'\\n'
结尾并被刷新。
最后,给出一些更笼统的注释:首先,您不希望客户端代码调用Logger::log
,而是调用一些宏,以便将__FILE__
和__LINE__
自动传递到创建的实际对象中。 对象本身可能应该是一个临时对象,其析构函数调用上面的finish例程。 最后,如果您处于多线程环境中,则可能要针对具有实际上无限的缓冲区(例如std::vector<char>
)的streambuf
,该缓冲区将忽略用户的任何刷新,然后在调用finish例程时,执行所有必要的操作以原子方式写入整个缓冲区,当然,要确保每个线程都具有上述所有组件的自己的实例。 (例如,如果您在Unix平台上,则必须保证Posix函数的write
是原子的,因此您不需要任何锁。)
我认为更直观的界面是:
Logger::log( LogLevels::WARNING ) << "Something happened!"
<< Logger::tabbedLine
<< "More Info";
因此,用户仍然可以使用'\\n'
或std::endl
到达下一行的开始,或者使用Logger::tabbedLine
(我承认这是一个不好的名字)来实现所需的功能。
您可以将std::ostream
子类化,并为operator<<
添加重载,该重载会收到一个特殊的类,其中tabbedLine
是tabbedLine
的静态实例。 您将需要在创建的流子类中“记住”所需的填充。
Logger::log
将返回此ostream
子类的实例。
您可以编写自己的流,流缓冲区和操纵器:
#include <iostream>
#include <iomanip>
#include <sstream>
// LogBuffer
// ============================================================================
class LogBuffer : public std::streambuf
{
// Types
// =====
public:
typedef typename std::streambuf buffer_type;
typedef typename buffer_type::char_type char_type;
typedef typename buffer_type::traits_type traits_type;
typedef typename buffer_type::int_type int_type;
typedef typename buffer_type::pos_type pos_type;
typedef typename buffer_type::off_type off_type;
// Construction/Destructiion
// =========================
public:
LogBuffer(buffer_type& buffer)
: m_buffer(buffer), m_indent(0), m_warning_count(0)
{}
public:
~LogBuffer() {
m_buffer.pubsync();
}
private:
LogBuffer(LogBuffer const&); // No Copy.
LogBuffer& operator=(LogBuffer const&); // No Copy.
// Functionality
// =============
public:
bool write(const std::string& s) {
return m_buffer.sputn(s.data(), s.size()) == std::streamsize(s.size());
}
bool warning() {
std::ostringstream out;
out << "[ WARNING: " << m_warning_count++ << "] ";
m_indent = out.str().size();
return write(out.str());
}
bool insert_indent() {
std::ostringstream out;
out << std::setw(m_indent) << "";
return write(out.str());
}
// Virtuell
// ========
protected:
std::streamsize xsputn(const char_type* s, std::streamsize n) {
return m_buffer.sputn(s, n);
}
int_type overflow(int_type ch) {
if(ch == traits_type::eof()) return traits_type::eof();
else return m_buffer.sputc(traits_type::to_char_type(ch));
}
int sync() {
return m_buffer.pubsync();
}
private:
buffer_type& m_buffer;
unsigned m_indent;
unsigned m_warning_count;
};
// LogStream
// ============================================================================
class LogStream : public std::ostream
{
// Types
// =====
private:
typedef std::ostream Base;
typedef LogBuffer buffer_type;
public:
typedef std::ostream stream_type;
typedef typename Base::char_type char_type;
typedef typename Base::traits_type traits_type;
typedef typename Base::int_type int_type;
typedef typename Base::pos_type pos_type;
typedef typename Base::off_type off_type;
// Construction
// ============
public:
LogStream()
: Base(&m_buffer), m_buffer(*std::clog.rdbuf())
{}
LogStream(stream_type& stream)
: Base(&m_buffer), m_buffer(*stream.rdbuf())
{}
private:
LogStream(const LogStream&); // No copy.
const LogStream& operator = (const LogStream&); // No copy.
private:
buffer_type m_buffer;
};
// Manipulator
// ===========
std::ostream& log_warning(std::ostream& stream) {
LogBuffer* buffer = dynamic_cast<LogBuffer*>(stream.rdbuf());
if(buffer) {
if( ! buffer->warning())
stream.setstate(std::ios_base::failbit);
}
return stream;
}
std::ostream& log_indent(std::ostream& stream) {
LogBuffer* buffer = dynamic_cast<LogBuffer*>(stream.rdbuf());
if(buffer) {
if( ! buffer->insert_indent())
stream.setstate(std::ios_base::failbit);
}
return stream;
}
int main() {
LogStream log;
log << log_warning
<< "First\n"
<< log_indent
<< "Second\n"
<< std::flush;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.