【问题标题】:easily throw away c++ call completely轻松完全抛弃 c++ 调用
【发布时间】:2013-07-13 21:22:02
【问题描述】:

我正在尝试实现在不需要时不会产生开销的日志记录(即根本不应该执行任何方法调用)。我想要 NO 开销,因为它是低延迟代码。我刚刚将#define ENABLE_LOGS 添加到我的头类中,现在看起来像这样(您可以忽略细节)

#pragma once

#include <string>
#include <fstream>

#define ENABLE_LOGS

namespace fastNative {

    class Logger
    {
    public:
        Logger(std::string name_, std::string fileName, bool append = false);
        ~Logger(void);
        void Error(std::string message, ...);
        void Debug(std::string message, ...);
        void DebugConsole(std::string message, ...);
        void Flush();
        static Logger errorsLogger;
        static Logger infoLogger;
    private:
        FILE* logFile;
        bool debugEnabled;
    };

}

每次我需要使用某种方法时,我都应该这样包围它:

#ifdef ENABLE_LOGS
    logger.Debug("seq=%6d len=%4d", seq, length_);
#endif

这是错误电话(我可以忘记环绕)并使代码变脏。我可以以某种方式修复我的代码,不每次都使用#ifdef吗?

在 C# 中我喜欢 Conditional 我想我需要类似的东西用于 c++。

【问题讨论】:

  • 查找任何 assert.h - 甚至是 stackoverflow.com/questions/9701229/… 的那个,以了解如何制作可以在一个地方用一个标志关闭的代码。
  • 您可以有条件地#define Debug 方法在标头中有一个 (inline) 空实现,然后依靠优化器删除什么都不做的函数调用(这假设任何参数都没有' t有副作用)。更传统的方法是直接使用预处理器宏,这种方法更直接,但更容易出错。
  • 我假设您也不希望对参数进行评估。如果您想要一个干净的 C++ 外观,这会使事情变得非常复杂。是时候做一些宏了!
  • 使用多态性并有一个什么都不做的实现,并使用您的宏选择正确的实现。
  • 有趣的是,您关心记录器的性能,但您的记录功能采用std::string 的值。无论如何,看看任何现有的日志库,你可能不会做得比这更好

标签: c++


【解决方案1】:

您可以将#ifdef 放在各个函数的主体中。这避免了TooTone的答案中的代码重复问题。

例子:

void fastNative::Logger::Debug(std::string message, ...)
{
#ifdef ENABLE_LOGS
    // do the actual logging
#endif
}

如果ENABLE_LOGS 没有定义,这个函数不会做任何事情。我建议您将const char* 而不是std::string 传递给这些方法。这样,如果未定义 ENABLE_LOGS,您就不必依赖编译器来创建多余的 std::string 对象。

【讨论】:

    【解决方案2】:

    一个不错的老把戏是:

    #ifdef ENABLE_LOGS
    #define LOG Logger.Debug
    #else
    #define LOG (void)sizeof
    #endif
    

    然后是代码:

    LOG("seq=%6d len=%4d", seq, length_);
    

    将扩展为:

    Logger.Debug("seq=%6d len=%4d", seq, length_);
    

    做日志。或者:

    (void)sizeof("seq=%6d len=%4d", seq, length_);
    

    那绝对没有任何作用。它甚至不评估函数参数!!!

    诀窍是第一个版本在函数调用中使用逗号作为参数分隔符。然而,在第二个版本中,它是一个未计算的逗号运算符。

    但是,一些编译器可能会给出关于无法访问代码的虚假警告。

    【讨论】:

      【解决方案3】:

      首先,看看已经有什么是有意义的。这是一个常见的问题,很多人以前都会解决过。例如,请参阅 stackoverflow question C++ logging framework suggestionsDr Dobbs A Highly Configurable Logging Framework In C++

      如果你自己动手,你应该从这样做中得到一些好主意。我过去使用过几种方法。一种是使语句本身有条件地定义

      #ifdef ENABLE_LOGS
      #define LOG(a,b,c) logger.Debug(a, b, c)
      #else
      #define LOG(a,b,c)
      #endif
      

      另一种方法是有条件地定义日志记录类本身。非日志版本将所有内容都作为空语句,您依赖编译器优化所有内容。

      #ifdef ENABLE_LOGS
      
      class Logger
      {
      public:
          Logger(std::string name_, std::string fileName, bool append = false);
          ~Logger(void);
          void Error(std::string message, ...);
          void Debug(std::string message, ...);
          void DebugConsole(std::string message, ...);
          void Flush();
          static Logger errorsLogger;
          static Logger infoLogger;
      private:
          FILE* logFile;
          bool debugEnabled;
      };
      
      #else
      
      class Logger
      {
      public:
          Logger(std::string name_, std::string fileName, bool append = false) {}
          ~Logger(void) {}
          void Error(std::string message, ...) {}
          void Debug(std::string message, ...) {}
          void DebugConsole(std::string message, ...) {}
          void Flush() {}
      };
      
      #endif
      

      您可以将 ENABLE_LOGSLogger 实现放在宏控制下的 cpp 文件中。这种方法的一个问题是您需要确保定义接口,以便编译器可以优化所有内容。因此,例如,使用 C 字符串参数类型 (const char*)。在任何情况下,const std::string&amp; 都比std::string 更可取(后者确保每次调用时都有一个字符串副本)。

      最后,如果您采用第一种方法,则应将所有内容封装在 do() { ... } while(0) 中,以确保在可能需要复合语句的地方使用宏时不会出现奇怪的行为。

      【讨论】:

      • 我并不总是调用变量logger。我使用不同的名称,有时我有多个可见的记录器
      • @javapowered 在这种情况下,对于第一种方法,您可以为宏设置另一个参数来指定哪个记录器对象。对于第二种方法,您不需要做任何不同的事情——您只需照常使用该类,但如果关闭日志记录,所有实现都是内联 null 并且您应该在发布版本中没有代码。
      • 第二种方法不一定会优化所有内容。例如,如果你有类似 debug.Log(..., foo()) 的东西,而 Log 什么都不做,那么函数 foo 仍然必须被调用(除非它在同一个翻译单元中并且没有副作用)。
      • @Kaz 同意这并不完美,我在回答中提出了一两个警告。你的观点很好,如果我有时间我会补充的。
      【解决方案4】:

      我经常看到的是使用#define来实际定义日志调用,例如:

      #define LOG_DEBUG(msg) logger.Debug(msg);
      

      但是您想将定义包装在启用或禁用日志记录的块中:

      #ifdef ENABLE_LOGS
      #define LOG_DEBUG(msg) logger.Debug(msg);
      #else
      #define LOG_DEBUG(msg)
      #endif
      

      您可以在代码中的任何位置调用 LOG_DEBUG。如果禁用了日志记录,调用 LOG_DEBUG 最终会在您的最终代码中显示为空行。

      【讨论】:

      • 如果你把它设为variadic macro,你的答案会更好。
      • 有几种方法可以定义它们,这在很大程度上是一个偏好问题。但是,这是一个不错的解决方案。
      【解决方案5】:

      有一种方法(llvm 的方式)使用宏来做到这一点。

      #ifdef ENABLE_LOGS
      #define DEBUG(ARG) do { ARG; } while(0)
      #else
      #define DEBUG(ARG)
      #endif
      

      然后将其用作:

      DEBUG(logger.Debug("seq=%6d len=%4d", seq, length_););
      

      【讨论】:

      • 在宏中执行...的目的是什么?
      • + @Monad:如果你想说if(test) DEBUG(...); else ...。它使它成为一个单一的声明。如果它扩展到{ARG;},您将在else 处收到语法错误。
      • 这就是为什么这种方法通常很糟糕的原因。
      • 不合理。这是唯一可以保证在编译时关闭日志记录时从日志调用的参数中消除所有评估的方法,即使参数调用外部函数也是如此。
      • @ Bartek Banachewicz :如果这种方法很糟糕,那么编译器编写者和标准库编写者就不会使用它。您提出的方法是一个众所周知的 #ifdefs 错误使用示例。
      猜你喜欢
      • 2018-11-24
      • 2021-11-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多