如何创建像 std::cout 这样的函数?
How to create functions like std::cout?
我正在为我的项目创建自己的日志记录实用程序,我想创建一个类似于 iostream 的 std::cout 的函数来记录到文件并打印到控制台。
这是我想要的:
enum
{
debug, error, warning, info
};
LOG(level) << "test"; // level - from the above enum
结果应该是这样的:
int iPlayerID = 1337;
LOG(info) << "Player " << iPlayerID << "Connected";
[Thu Jan 29 18:32:11 2015] [info] Player 1337 Connected
您可以定义一个 enum
如
enum loglevel_en {
log_none, log_debug, log_info, log_waring, log_error };
然后有一个全局变量:
enum loglevel_en my_log_level;
并提供一些设置方法(例如从程序参数)。
最后,定义一个宏(在全局头中)
#define MY_LOG(Lev,Thing) do { if (my_log_level >= Lev) \
std::cout << __FILE__ << ":" << __LINE__ \
<< " " << Thing << std::endl; } while(0)
并像
一样使用它
MY_LOG(log_info, "x=" << x)
随时改进 MY_LOG
宏以输出更多内容(日期等...)
您可以改为定义
#define LOG(Lev) if (my_log_level >= Lev) std::cout
但这对像
这样的代码来说效果不佳
if (x>34)
LOG(log_info) << "strange x=" << x;
else return;
所以我强烈建议MY_LOG
std::cout
不是函数,它是 std::ostream
类型的对象,它重载了 operator<<
.
如何操作的简要说明:
enum Level {
debug, error, warning, info
};
struct Logger {
std::ostream* stream; // set this in a constructor to point
// either to a file or console stream
Level debug_level;
public:
Logger& operator<<(const std::string& msg)
{
*stream << msg; // also print the level etc.
return *this;
}
friend Logger& log(Logger& logger, Level n);
{
logger.debug_level = n;
return logger;
}
};
Ant 然后像这样使用它
Logger l;
log(l, debug) << "test";
我不会在这里输入编码细节,但我会为您提供一些快速指南:
根据枚举创建一个单例对象池(记录器可以创建一个单例)或命名空间或 returns 某个日志 class :
记录器& SingletonLoggersManager::GetLoggerForLevel(eLogLevel);
为您的 class 覆盖“<<”运算符,以便允许在您的记录器中根据您的需要输出 class
https://msdn.microsoft.com/en-us/library/1z2f6c2k.aspx
定义一个宏以便能够在您的代码中进行快速调用:
#define LOG(x) SingletonLogger::GetLoggerForLevel(eLogLoevel);
现在,当您在代码中使用时
Log(debug) << "test"
它将扩展为:
(SingletonLogger::GetLoogerForLevel(debug)) << "test";
诀窍是让你的 LOG(level)
到 return 一个特殊的类型
包含指向 std::ostream
的指针,并定义 <<
运算符。
类似于:
class LogStream
{
std::ostream* myDest;
public:
LogStream( std::ostream* dest ) : myDest( dest ) {}
template <typename T>
LogStream& operator<<( T const& obj )
{
if ( myDest != nullptr ) {
*myDest << obj;
}
return *this;
}
};
LOG(level)
宏创建一个实例,类似于:
#define LOG(level) LogStream( getLogStream( level, __FILE__, __LINE__ ) )
当然,getLogStream
可以在调用时插入任何它想要的信息(如时间戳)。
您可能想在 LogStream
的析构函数中添加刷新。
上面我看到了两个问题。第一个是分叉您的消息(到文件和控制台)。第二个是用一些额外的东西包装写的东西。
meta_stream
处理 operator<<
重载。它使用 CRTP 静态分派给它的子类型:
template<class D, class substream>
struct meta_stream {
D& self() { return *static_cast<D*>(this); } // cast myself to D
// forwarders of operator<<
template<class X>
friend D& operator<<( meta_stream<D>& d, X const& x ) {
d.self().write_to(x);
return d.self();
}
friend D& operator<<(
meta_stream<D>& d,
substream&(*mod_func)(substream&)
) {
d.self().write_to(mod_func);
return d.self();
}
};
由于 std::endl
和其他修饰符的工作方式,我不得不重写 <<
两次——它们是重载函数的名称。
这解决了将相同的字符串输出到两个不同的 ostream 的问题:
template<class substream>
struct double_ostream:
meta_stream<double_ostream<substream>,substream>
{
substream* a = nullptr;
substream* b = nullptr;
template<class X>
void write_to( X&&x ) {
if (d.a) (*d.a) << x;
if (d.b) (*d.b) << std::forward<X>(x);
}
double_ostream( std::basic_ostream<CharT>* a_, std::basic_ostream<CharT>* b_ ):
a(a_), b(b_)
{}
double_ostream(double_ostream const&)=default;
double_ostream()=default;
double_ostream& operator=(double_ostream const&)=default;
};
注意通过 meta_stream
使用 CRTP。我只需要执行 write_to
.
首先,将您的 4 个记录器写入此数组:
enum loglevel {
debug, error, warning, info
};
double_stream<std::ostream> loggers[4];
为每个指针提供一个指向 std::cout
的指针和一个指向(存储在别处的)流的指针,这些流包装了您要将日志保存到的文件。如果您不想将该级别记录到该输出流(例如,在发布中,跳过调试日志),您可以传递 nullptr
,并且您可以将内容记录到不同的日志文件(调试到一个文件,信息到另一个)。
double_stream<std::ostream> log( loglevel l ) {
double_stream<std::ostream> retval = loggers[l];
std::string message;
// insert code to generate the current date here in message
// insert code to print out the log level here into message
retval << message;
return retval;
}
现在 log(debug) << "hello " << "world\n"
会为您写留言。
如果您不想在日志消息末尾写换行符,您可以做更多花哨的事情,但我怀疑这样做是否值得。只写换行符。
如果您真的想要该功能:
template<class substream>
struct write_after_ostream:
meta_stream<write_after_ostream<substream>,substream>
{
substream* os = nullptr;
template<class X>
void write_to( X&&x ) {
if (os) *os << std::forward<X>(x);
}
~write_after_ostream() {
write_to(message);
}
write_after_ostream( substream* s, std::string m ):
os(s), message(m)
{}
std::string message;
}
write_after_ostream<double_stream<std::ostream>> log( loglevel l ) {
// note & -- store a reference to it, as we will be using a pointer later:
double_stream<std::ostream>& retval = loggers[l];
std::string pre_message;
// insert code to generate the current date here in pre_message
// insert code to print out the log level here into pre_message
retval << pre_message;
return {&retval, "\n"};
}
但我认为不值得。
只有一个像样的解决方案,它并不简单,但 LOG(log_info) << "line 1\nline 2" << std::endl;
正确。
您必须实施自定义std::ostreambuf
。这是唯一可以在 应用所有常用 operator<<
函数后重新格式化输出 的 class。
特别是,当您有一堆字符要处理时,您的流缓冲区方法 overflow
会被调用。您现在可以添加日志级别过滤,并可靠地检查格式化字符流中的换行符。
我正在为我的项目创建自己的日志记录实用程序,我想创建一个类似于 iostream 的 std::cout 的函数来记录到文件并打印到控制台。
这是我想要的:
enum
{
debug, error, warning, info
};
LOG(level) << "test"; // level - from the above enum
结果应该是这样的:
int iPlayerID = 1337;
LOG(info) << "Player " << iPlayerID << "Connected";
[Thu Jan 29 18:32:11 2015] [info] Player 1337 Connected
您可以定义一个 enum
如
enum loglevel_en {
log_none, log_debug, log_info, log_waring, log_error };
然后有一个全局变量:
enum loglevel_en my_log_level;
并提供一些设置方法(例如从程序参数)。
最后,定义一个宏(在全局头中)
#define MY_LOG(Lev,Thing) do { if (my_log_level >= Lev) \
std::cout << __FILE__ << ":" << __LINE__ \
<< " " << Thing << std::endl; } while(0)
并像
一样使用它 MY_LOG(log_info, "x=" << x)
随时改进 MY_LOG
宏以输出更多内容(日期等...)
您可以改为定义
#define LOG(Lev) if (my_log_level >= Lev) std::cout
但这对像
这样的代码来说效果不佳if (x>34)
LOG(log_info) << "strange x=" << x;
else return;
所以我强烈建议MY_LOG
std::cout
不是函数,它是 std::ostream
类型的对象,它重载了 operator<<
.
如何操作的简要说明:
enum Level {
debug, error, warning, info
};
struct Logger {
std::ostream* stream; // set this in a constructor to point
// either to a file or console stream
Level debug_level;
public:
Logger& operator<<(const std::string& msg)
{
*stream << msg; // also print the level etc.
return *this;
}
friend Logger& log(Logger& logger, Level n);
{
logger.debug_level = n;
return logger;
}
};
Ant 然后像这样使用它
Logger l;
log(l, debug) << "test";
我不会在这里输入编码细节,但我会为您提供一些快速指南:
根据枚举创建一个单例对象池(记录器可以创建一个单例)或命名空间或 returns 某个日志 class :
记录器& SingletonLoggersManager::GetLoggerForLevel(eLogLevel);
为您的 class 覆盖“<<”运算符,以便允许在您的记录器中根据您的需要输出 class
https://msdn.microsoft.com/en-us/library/1z2f6c2k.aspx
定义一个宏以便能够在您的代码中进行快速调用:
#define LOG(x) SingletonLogger::GetLoggerForLevel(eLogLoevel);
现在,当您在代码中使用时
Log(debug) << "test"
它将扩展为:
(SingletonLogger::GetLoogerForLevel(debug)) << "test";
诀窍是让你的 LOG(level)
到 return 一个特殊的类型
包含指向 std::ostream
的指针,并定义 <<
运算符。
类似于:
class LogStream
{
std::ostream* myDest;
public:
LogStream( std::ostream* dest ) : myDest( dest ) {}
template <typename T>
LogStream& operator<<( T const& obj )
{
if ( myDest != nullptr ) {
*myDest << obj;
}
return *this;
}
};
LOG(level)
宏创建一个实例,类似于:
#define LOG(level) LogStream( getLogStream( level, __FILE__, __LINE__ ) )
当然,getLogStream
可以在调用时插入任何它想要的信息(如时间戳)。
您可能想在 LogStream
的析构函数中添加刷新。
上面我看到了两个问题。第一个是分叉您的消息(到文件和控制台)。第二个是用一些额外的东西包装写的东西。
meta_stream
处理 operator<<
重载。它使用 CRTP 静态分派给它的子类型:
template<class D, class substream>
struct meta_stream {
D& self() { return *static_cast<D*>(this); } // cast myself to D
// forwarders of operator<<
template<class X>
friend D& operator<<( meta_stream<D>& d, X const& x ) {
d.self().write_to(x);
return d.self();
}
friend D& operator<<(
meta_stream<D>& d,
substream&(*mod_func)(substream&)
) {
d.self().write_to(mod_func);
return d.self();
}
};
由于 std::endl
和其他修饰符的工作方式,我不得不重写 <<
两次——它们是重载函数的名称。
这解决了将相同的字符串输出到两个不同的 ostream 的问题:
template<class substream>
struct double_ostream:
meta_stream<double_ostream<substream>,substream>
{
substream* a = nullptr;
substream* b = nullptr;
template<class X>
void write_to( X&&x ) {
if (d.a) (*d.a) << x;
if (d.b) (*d.b) << std::forward<X>(x);
}
double_ostream( std::basic_ostream<CharT>* a_, std::basic_ostream<CharT>* b_ ):
a(a_), b(b_)
{}
double_ostream(double_ostream const&)=default;
double_ostream()=default;
double_ostream& operator=(double_ostream const&)=default;
};
注意通过 meta_stream
使用 CRTP。我只需要执行 write_to
.
首先,将您的 4 个记录器写入此数组:
enum loglevel {
debug, error, warning, info
};
double_stream<std::ostream> loggers[4];
为每个指针提供一个指向 std::cout
的指针和一个指向(存储在别处的)流的指针,这些流包装了您要将日志保存到的文件。如果您不想将该级别记录到该输出流(例如,在发布中,跳过调试日志),您可以传递 nullptr
,并且您可以将内容记录到不同的日志文件(调试到一个文件,信息到另一个)。
double_stream<std::ostream> log( loglevel l ) {
double_stream<std::ostream> retval = loggers[l];
std::string message;
// insert code to generate the current date here in message
// insert code to print out the log level here into message
retval << message;
return retval;
}
现在 log(debug) << "hello " << "world\n"
会为您写留言。
如果您不想在日志消息末尾写换行符,您可以做更多花哨的事情,但我怀疑这样做是否值得。只写换行符。
如果您真的想要该功能:
template<class substream>
struct write_after_ostream:
meta_stream<write_after_ostream<substream>,substream>
{
substream* os = nullptr;
template<class X>
void write_to( X&&x ) {
if (os) *os << std::forward<X>(x);
}
~write_after_ostream() {
write_to(message);
}
write_after_ostream( substream* s, std::string m ):
os(s), message(m)
{}
std::string message;
}
write_after_ostream<double_stream<std::ostream>> log( loglevel l ) {
// note & -- store a reference to it, as we will be using a pointer later:
double_stream<std::ostream>& retval = loggers[l];
std::string pre_message;
// insert code to generate the current date here in pre_message
// insert code to print out the log level here into pre_message
retval << pre_message;
return {&retval, "\n"};
}
但我认为不值得。
只有一个像样的解决方案,它并不简单,但 LOG(log_info) << "line 1\nline 2" << std::endl;
正确。
您必须实施自定义std::ostreambuf
。这是唯一可以在 应用所有常用 operator<<
函数后重新格式化输出 的 class。
特别是,当您有一堆字符要处理时,您的流缓冲区方法 overflow
会被调用。您现在可以添加日志级别过滤,并可靠地检查格式化字符流中的换行符。