4

我有一个 C++ 类,我在其中放置了许多 std::cout 语句来打印有关该类正在处理的大量信号的信息性文本消息。我的意图是将这些文本消息重定向到一个名为 log 的函数。在这个函数中,我有一个名为 mVerbose 的标志,它定义了是否应该打印日志文本。该函数的内容如下:

 void XXXProxy::log(std::stringstream& ss)
 {
   if(mVerbose)
   {
     std::cout << ss;
     ss << "";
   }
 }

那么,该函数的调用者代码片段如下: std::stringstream logStr;

 logStr << "SE"
        << getAddr().toString()
        << ": WAITING on epoll..."
        << std::endl;      
 log(logStr);

我想以一种可以摆脱创建 std::stringstream 对象并调用 log 函数的方式重载我的 XXXProxy 中的 << 运算符。我希望能够如下记录文本消息,并让 << 运算符将所有内容聚合到:

 << "SE"
 << getAddr().toString()
 << ": WAITING on epoll..."
 << std::endl;      

所以我希望有一个成员 << 函数,如下所示:

 void XXXProxy::operator << (std::stringstream& ss)
 {
   if(mVerbose)
   {
     std::cout << ss;
     ss << "";
   }
 } 

问题

我是一个相对新手 C++ 开发人员,在尝试编写上述 << 运算符时遇到很多编译错误。您能否提出一些建议或指导我访问一些链接,以便我正确实施此 << 运算符。谢谢。

4

2 回答 2

7

如果您不想std::cout直接使用并且想要拥有自己的 Log 类,则可以实现一个简单的包装器,提供与std::ostream: operator<<相同的接口:

class Log {
private: 
    std::ostream& _out_stream;

    //Constructor: User provides custom output stream, or uses default (std::cout).
    public: Log(std::ostream& stream = std::cout): _out_stream(stream) {} 

    //Implicit conversion to std::ostream
    operator std::ostream() {
        return _out_stream;
    } 

    //Templated operator>> that uses the std::ostream: Everything that has defined 
    //an operator<< for the std::ostream (Everithing "printable" with std::cout 
    //and its colleages) can use this function.    
    template<typename T> 
    Log& operator<< (const T& data) 
    {
        _out_stream << data;
    }
}

所以如果你std::ostream& operator>>(std::ostream& os , const YourClass& object)为你的类实现,你可以使用这个 Log 类。

这种方法的优点是您使用相同的机制来std::cout << your_class_object工作,并使类与 Log 一起工作。

例子:

struct Foo
{
    int x = 0; //You marked your question as C++11, so in class initializers 
               //are allowed. 

    //std::ostream::operator<< overload for Foo:
    friend std::ostream& operator<<(std::ostream& os , const Foo& foo)
    {
        os << foo.x;
    }
};

int main()
{
  Log my_log;
  Foo my_foo;

  my_foo.x = 31415;

  my_log << my_foo << std::endl; //This prints "31415" using std::cout.
}

可能的改进:

  • 您可以编写一个extern constLog 类,并使该类实现一个单例。这使您可以在程序中的任何地方访问日志。
  • 在日志输出中通常有一个标题,比如Log output (17:57): log message. 为此,您可以将std::endl其用作哨兵并存储一个标志,说明下一个输出何时是行的开头(日志消息的开头)。查看下一个答案以获得完整且有效的实施

参考:

于 2013-07-11T15:39:49.860 回答
7

该示例的时间戳只是一个示例:)。

但如果你喜欢,我们可以尝试实现它。感谢 C++11 及其 STL 的重大改进,我们有一个出色的时间/日期 API:std::chrono

std::chrono基于三个方面:

  • 时钟
  • 持续时间
  • 时间点

此外,chrono 提供三种类型的时钟,std::system_clockstd::steady_clockstd::high_resolution_clock。在我们的例子中,我们使用std::system_clock(我们想要访问日期时间,而不是测量精确的时间间隔)。
有关 std::chrono 的更多信息,请查看这个可怕的 Bo Qian 的 youtube 教程

所以如果我们必须为我们的日志头实现一个时间戳,我们可以这样做:

编辑:像其他好东西一样,C++ 模板是很好的工具,直到你过度使用它。
我们的问题是这std::endl是一个模板函数,所以我们不能将它作为参数直接传递给另一个模板函数(operator<<在我们的例子中),因为编译器不能直接推断 std::endl 模板参数。那就是经常出现的错误“未解决的重载函数类型”

但是有一种更简单的方法可以做到这一点:使用operator<<for std::endlonly 的显式重载,而其他模板则用于其他所有内容:

class Log
{
private:
    std::ostream& _out_stream;
    bool         _next_is_begin;

    const std::string _log_header;
    using endl_type = decltype( std::endl ); //This is the key: std::endl is a template function, and this is the signature of that function (For std::ostream).

public:
    static const std::string default_log_header;

    //Constructor: User passes a custom log header and output stream, or uses defaults.
    Log(const std::string& log_header = default_log_header , std::ostream& out_stream = std::cout) : _log_header( log_header ) , _out_stream( out_stream ) , _next_is_begin( true ) {}

    //Overload for std::endl only:
    Log& operator<<(endl_type endl)
    {
        _next_is_begin = true;

        _out_stream << endl;

        return *this;
    }

    //Overload for anything else:
    template<typename T>           
    Log& operator<< (const T& data) 
    {
        auto now        = std::chrono::system_clock::now();
        auto now_time_t = std::chrono::system_clock::to_time_t( now ); //Uhhg, C APIs...
        auto now_tm     = std::localtime( &now_time_t ); //More uhhg, C style... 

        if( _next_is_begin )
            _out_stream << _log_header << "(" << now_tm->tm_hour << ":" << now_tm->tm_min << ":" << now_tm->tm_sec << "): " << data;
        else
            _out_stream << data;

        _next_is_begin = false;

        return *this;
    }
};

const std::string Log::default_log_header = "Log entry";

此代码片段完美运行。我已将完整的实现推送到我的 github 帐户

参考:

于 2013-07-11T23:37:02.470 回答