8

我正在尝试实现在不需要时不会产生开销的日志记录(即根本不应该执行任何方法调用)。我想要 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# 中,我喜欢有条件的,我想我需要类似的东西用于 c++。

4

5 回答 5

13

首先,看看那里已经有什么是有意义的。这是一个常见的问题,很多人以前都会解决过。例如,请参阅stackoverflow 问题 C++ 日志框架建议Dobbs 博士 A High 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

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

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

于 2013-07-15T21:19:14.813 回答
7

有一种方法(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_););
于 2013-07-15T21:12:25.777 回答
3

我经常看到的是使用#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 最终会在最终代码中显示为空行。

于 2013-07-15T21:13:57.860 回答
0

一个不错的老把戏是:

#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_);

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

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

然而,一些编译器可能会给出关于不可访问代码的虚假警告。

于 2013-07-17T19:37:39.087 回答
0

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

例子:

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

如果ENABLE_LOGS未定义,则此函数不执行任何操作。我建议您将 aconst char*而不是传递std::string给这些方法。这样,如果ENABLE_LOGS未定义,您就不必依赖编译器来不创建冗余std::string对象。

于 2013-07-17T21:00:57.710 回答