【问题标题】:operator << - how to detect last argument运算符 << - 如何检测最后一个参数
【发布时间】:2011-03-30 15:47:11
【问题描述】:

我正在用 C++ 编写一个日志类。这个类是一个单例。我想以这样的方式添加日志:

Log::GetInstance() << "Error: " << err_code << ", in class foo";

好的,在 Log 对象中,我想在最后一个参数出现时保存整行(本例中为“, in class foo”)。

如何检测最后一个

我不使用任何结束标签。

【问题讨论】:

  • 我不认为重载运算符
  • @Falmarri:我其实很喜欢这种方法。这就是 Qt 使用其QDebug 类的方式。
  • 而 QT 正在与该语言对抗。仅仅因为他们可以并不意味着他们应该。
  • @C Johnson:你对 C++ 流也有同感吗?

标签: c++ logging operator-keyword


【解决方案1】:

您可以通过不使用单例来解决此问题。如果你做一个这样的函数:

Log log()
{
    return Log();
}

您几乎可以像以前一样添加日志:

log() << "Error: " << err_code << ", in class foo";

不同之处在于Log 对象的析构函数在这一行之后被调用。因此,现在您可以检测到最后一个参数何时被处理。

【讨论】:

  • 某个对象仍然需要维护打开的文件句柄……
  • @Potatoswatter:是的。由于他使用的是单例,我想说让它成为Log 类的静态成员。其他解决方案当然是可能的。
  • 我喜欢使用构造函数和析构函数的 RAII 技术,我认为它真的很优雅。
【解决方案2】:

我会让你的Log::GetInstance 返回一个代理对象而不是日志对象本身。代理对象会保存写入它的数据,然后在其析构函数中,它实际上会将累积的数据写入日志。

【讨论】:

  • +1:虽然单例可能很难看,但最好给出不需要不必要重构的建议。
【解决方案3】:

你让 Log 在运算符 之后返回一个不同的对象

template<typename T>
LogFindT operator<<(Log aLog, T const& data)
{
    // Put stuff in log.
    log.putStuffInLog(data);

    // now return the object to detect the end of the statement.
    return LogFindT(aLog);
}


struct LogFindT
{
    LogFindT(Log& aLog) : TheLog(aLog) {}
    Log& TheLog;
    ~LogFindT()
    {
        // Do stuff when this object is eventually destroyed
        // at the end of the expression.
    }
};

template<typename T>
LogFindT& operator<<(LogFindT& aLog, T const& data)
{
     aLog.TheLog.putStuffInLog(data);

     // Return a reference to the input so we can chain.
     // The object is thus not destroyed until the end of the stream.
     return aLog;
}

【讨论】:

    【解决方案4】:

    我认为 Jerry 和 Martin 给出了最好的建议,但为了完整起见,我首先想到的是 std::endl

    如果您通过自定义streambuf 类在iostream 系统中实现Log,则只需在行尾添加&lt;&lt; endl&lt;&lt; flush。既然你在问,我想你没有。

    但你可以模仿endl 的工作方式。添加一个操纵器处理程序

    Log &operator<< ( Log &l, Log & (*manip)( Log & ) )
        { return manip( l ); } // generically call any manipulator
    
    Log &flog( Log &l ) // define a manipulator "flush log"
        { l->flush(); return l; }
    

    或添加专用的operator&lt;&lt;

    struct Flog {} flog;
    
    Log &operator<< ( Log &l, Flog )
        { l->flush(); return l; }
    

    【讨论】:

      【解决方案5】:

      不要对操作员太聪明。在有意义的情况下,您应该重载运算符。在这里你不应该。 这看起来很奇怪。

      你应该有一个看起来像这样的静态方法:

      Log::Message( message_here );
      

      需要一个 std::string。然后客户会头疼地弄清楚如何组装错误字符串。

      【讨论】:

      • @David:在我的书中说“不要这样做”而不是“这就是如何”的答案是不好的。对我来说,不分享知识是一件可怕的事情。当有人想做某事一种不好的方式(“什么是 typedef 指针最干净的方式?”->“不要[因为它总是令人困惑]”),那很好,但你或我可以很容易地使这个界面易于使用,那么为什么不传播呢?我不知道,您唯一的论点似乎是“有人可能不明白,所以不要这样做。”所以没有人应该写任何代码,因为不是每个人都懂代码?为什么要特别呼吁运营商?
      • @David:呃,你是说typedef some_type* some_type_ptr吗?一般情况下,唯一好的情况是使用shared_ptr 或其他东西,但标记单个星号会令人困惑。我觉得您没有将实现与接口分开,我们可以同意存在区别吗?运算符提供一个接口。执行 OP 想要的操作(使用 operator&lt;&lt; 将一些数据插入流中)与使用 C++ 流(使用 operator&lt;&lt; 将一些数据插入流中)相比,并没有更不直观。接口有什么区别?说“OP 检测到结束并做某事”是不是改变...
      • ...在界面中,用法完全一样。正如@Job 所说,他在界面中使用operator&lt;&lt; 是一个非常好的选择。唯一剩下的就是实现了。我们都知道 很多 在 C++ 流的底层实现了 operator&lt;&lt; 工作,我们对此很好,接口 是相同的。同样,随着 OP 的代码返回一个代理以在后台工作,interface 是相同的。接口中的任何内容都不受实现细节的影响。没有关于他的操作员使用或界面让我们得出结论他的界面很糟糕,这正是......
      • ...与 C++ 流相同。实现显然不同,因为它代理插入,但这只是抽象层。你不能说这很糟糕,即使你这样做了,你也不会再争论操作符的使用,这在 interface 中。您可以说“让用户使用endl 终止该行,而不是检测它。”,但请注意该句子中的任何内容都与运营商无关。无论如何我不同意这一点,使界面易于使用是重中之重,同样我们不希望人们必须记住delete,这对他来说会很好......
      • ...用户不必记住endl。并且有一个非常简单的解决方案,那么为什么不使用它呢?问题是关于实现的,operator&lt;&lt; 属于接口,所以提到它并不能回答问题。
      【解决方案6】:

      没有什么好方法可以做你想做的事。 C 和 C++ 根本不是面向行的语言。没有像“代码行”或任何更大的单元,也没有以任何方式组合的链式调用。

      在 C++ 中,表达式“a

       t1 = a;
       t2 = t1.operator<<(b);
       t3 = t2.operator<<(c);
       t4 = t3.operator<<(d);
      

      这就是 C++ ostream 使用 endl 作为显式行尾标记的原因;否则就没有像样的方法。

      【讨论】:

      • 顺便说一句,我感谢所有其他提出方法的答案。创建短暂的临时对象和代理等是一种有趣的学术转移,但在实践中,如果出现任何问题,它将产生额外的运行时开销并使调试(您的日志记录和调试系统!)复杂化。我真的不想去那里,因为如果您对它的理解不够好,无法自己提出这个想法,那只是一种更复杂的方式来打自己的脚。所以我仍然坚持我的主张——没有好的方法可以做你想做的事。 :-)
      • 所以基本上不是教他们,而是说“你想不通,不要尝试。”?此外,这些代理将被过去十年制造的任何编译器完全内联,没有任何开销。即使有,干净的界面也比快速的界面要好。 (人们总是可以使用分析器(不是猜测)快速制作干净的界面,但要制作干净的快速界面要困难得多。)
      • 我不同意(与 Drew)。创建临时对象不是一种学术实践,但在商业代码中经常出现。没有显着的开销(互联网上的另一个谣言),调试起来也不难(因为第一次写相对容易(所以没有错误))。
      【解决方案7】:

      这是基于@martin-york 回答的解决方案。稍作修改以在结构中使用成员运算符。

      #include<sstream>
      #include<iostream>
      
      struct log_t{
          void publish(const std::string &s){
              std::cout << s << std::endl;
          }
      };
      
      struct record_t{
      
          struct record_appender_t
          {
              record_appender_t(record_t& record_) : record(record_) {}
              record_t& record;
              ~record_appender_t()
              {
                  // Do stuff when this object is eventually destroyed
                  // at the end of the expression.
                  record.flush();
              }
      
              template<typename T>
              record_appender_t& operator<<(T const& data)
              {
                  record.stream() << data;
                  // Return a reference to the input so we can chain.
                  // The object is thus not destroyed until the end of the stream.
                  return *this;
              }
          };
      
          std::ostringstream message;
          log_t log;
          void flush(){
              log.publish(message.str());
          }
          std::ostringstream& stream() {
              return message;
          }
          template<typename T>
          record_appender_t operator<<(T const& data)
          {
              // Put stuff in log.
              message << data;
      
              // now return the object to detect the end of the statement.
              return record_appender_t(*this);
          }
      };
      
      #define LOG \
          record_t()
      
      int main(){
          LOG << 1 << 2 << "a";
      }
      

      【讨论】:

        猜你喜欢
        • 2011-12-01
        • 1970-01-01
        • 2022-07-06
        • 2014-07-23
        • 1970-01-01
        • 2019-10-27
        • 2021-12-17
        • 2023-03-10
        • 1970-01-01
        相关资源
        最近更新 更多