C++如何在运行时启用/禁用std :: cout的调试信息

22

有没有一种方法可以在程序内部使用std :: cout定义/取消定义调试消息?

我知道有像#define,#ifndef这样的东西,但我想知道是否有更简洁的方式来定义一个变量,比如:

# debug ON

这会打印出所有的调试数据(使用std::cout)。因此,我们的调试代码看起来像这样:

#ifndef DEBUG
// do something useful
#endif

当你编写数百个调试代码时,我发现上述代码很麻烦。

谢谢!

Carlo

9个回答

47
#ifdef DEBUG
#define DEBUG_MSG(str) do { std::cout << str << std::endl; } while( false )
#else
#define DEBUG_MSG(str) do { } while ( false )
#endif

int main()
{
    DEBUG_MSG("Hello" << ' ' << "World!" << 1 );
    return 0;
}

这不是你可以在运行时更改的东西。如果你所说的“on the fly”是指在编译时,那么这是一个非常简单和好的答案满足你的需求。如果你需要在运行时更改它,日志库被设计为可以通过某些文件进行配置,而不是编译时标志。 - NG.
@SB 我理解“on the fly”的意思是编译时,因为他使用了#ifndef debug这个条件编译指令。 - Gianni
13
这里的do while的目的是什么? - cYrus
13
为了强制你每次在宏调用的末尾添加一个分号“;”,确保当宏被扩展为“空”文本(即没有“;”)时,if(c)MACRO(param)\func(other);不会变成if(c) func(other);。 - Gianni
1
@Milan:请参阅 https://dev59.com/qXVC5IYBdhLWcg3w4Vb6。 - Christian Severin
显示剩余5条评论

8

除非您有复杂的日志记录需求,否则一些日志记录库可能会很重。这里是我刚刚拼凑出来的东西。需要进行一些测试,但可能符合您的要求:

#include <cstdio>
#include <cstdarg>

class CLog
{
public:
    enum { All=0, Debug, Info, Warning, Error, Fatal, None };
    static void Write(int nLevel, const char *szFormat, ...);
    static void SetLevel(int nLevel);

protected:
    static void CheckInit();
    static void Init();

private:
    CLog();
    static bool m_bInitialised;
    static int  m_nLevel;
};

bool CLog::m_bInitialised;
int  CLog::m_nLevel;

void CLog::Write(int nLevel, const char *szFormat, ...)
{
    CheckInit();
    if (nLevel >= m_nLevel)
    {
        va_list args;
        va_start(args, szFormat);
        vprintf(szFormat, args);
        va_end(args);
    }
}
void CLog::SetLevel(int nLevel)
{
    m_nLevel = nLevel;
    m_bInitialised = true;
}
void CLog::CheckInit()
{
    if (!m_bInitialised)
    {
        Init();
    }
}
void CLog::Init()
{
    int nDfltLevel(CLog::All);
    // Retrieve your level from an environment variable, 
    // registry entry or wherecer
    SetLevel(nDfltLevel);
}

int main()
{
    CLog::Write(CLog::Debug, "testing 1 2 3");
    return 0;
}

谢谢!我会研究一下。这看起来适合我的小程序! - user402642
不进行任何锁定。如果您的程序具有大量多线程,您可能需要这样做。 - Michael J

5

另一个简单的解决方案是,在调试模式下打开对coutstd::ostream引用,并在非调试模式下打开/dev/null,如下所示:

在debug.h文件中:

extern std::ostream &dout;

在 debug.c 中
#ifdef DEBUG
std::ostream &dout = cout;
#else
std::ofstream dev_null("/dev/null");
std::ostream &dout = dev_null;
#endif

然后:
dout << "This is a debugging message";

当然,这仅适用于任何系统,其中/dev/null指向空设备。由于此处引用是全局的,因此它类似于cout。通过这种方式,您可以将相同的流指向多个输出流,例如日志文件,具体取决于调试标志的值等。

这个回答很有趣,但我必须问如何在Windows上实现相同的功能。 - Petross404

5

可能不是最好的选择。我建议使用一个日志库。我不确定C++现在最好的选择是什么,但我以前用过log4cpp,觉得它还不错。

编辑:我假设“on the fly”意味着在运行时。如果你只需要它作为编译时标志,那么Gianni的答案可能是最容易实现的。然而,日志库给你很多灵活性,并允许在运行时重新配置。


2
+1. log4cpp有点老了,我建议调查一下log4cxx、Boost.Log候选项之一或Pantheios。 - Josh Kelley
@Carlo - 你可能想要查看Josh建议的那些。我知道log4cpp有点老了。 - NG.

3

虽然这个问题很旧,也有一些好的答案,但我想发布一个解决方案。它类似于Giannis的方法,但不同。而且,我使用了std::cerr而不是std::cout,但你可以很快地更改它。

#include <iostream>
#ifdef DEBUG
#  define DEBUG_LOG std::cerr

#else
class log_disabled_output {};
static log_disabled_output log_disabled_output_instance;

template<typename T>
log_disabled_output& operator << (log_disabled_output& any, T const& thing) { return any; }

// std::endl simple, quick and dirty
log_disabled_output& operator << (log_disabled_output& any, std::ostream&(*)(std::ostream&)) { return any; }

#  define DEBUG_LOG log_disabled_output_instance 
#endif

int main() {
    int x=0x12345678;
    DEBUG_LOG << "my message " << x << " " << "\n more information" << std::endl;
};

现在,您可以像使用输出流一样使用它。
(注意:仅在使用cerr时才包含iostream。如果您没有已经包含它,这将减少包含量。-编辑:不支持std :: endl)。如果定义了DEBUG,则使用cerr打印错误。否则,静态实例化虚设类log_disabled_output,并对任何类型重载operator <<。优点是:如果禁用记录,聪明的编译器将注意到流无事可做并优化整个“行”以消除任何开销,因此如果禁用DEBUG,则没有任何开销。

我不能在这个DEBUG_LOG中使用endl,是吗? - ephemerr
@ephemerr:不是之前的版本,而是使用额外的重载。请看我的编辑。请注意,这只是一个指向方向的示例,不是实际的完整实现。如果需要,您可以进行扩展。 - user1810087

2

我也想做同样的事情。经过一些研究,我开发了以下内容,并且似乎可以正常工作。如果您发现任何问题,请在评论区留言。

ostream DbgMsg(NULL);
enum {
  DBGMSG_NONE,
  DBGMSG_DEFAULT,
  DBGMSG_VERBOSE
} DbgLvl = DBGMSG_DEFAULT;

ostream &DbgMsgDefault(ostream &stream) {
  return (DbgLvl>=DBGMSG_DEFAULT) ? cout : stream;
}

ostream &DbgMsgVerbose(ostream &stream) {
  return (DbgLvl>=DBGMSG_VERBOSE) ? cout : stream;
}

void main() {
   DbgMsg<<DbgMsgDefault<<"default:default"<<endl;
   DbgMsg<<DbgMsgVerbose<<"default:verbose"<<endl;
   DbgLvl = DBGMSG_NONE;
   DbgMsg<<DbgMsgDefault<<"none:default"<<endl;
}

欢迎来到 Stack Overflow!您是否考虑添加一些叙述来解释这段代码是如何工作的,以及它是如何回答问题的?对于提问者和其他人来说,这将非常有帮助。 - Andrew Barber

1

这是我使用的代码(在VC++中工作)- 这里“##”用于连接字符串

#ifdef DEBUG 
#define pout cout 
#else
#define pout / ## / cout 
#endif 

对于其他编译器,请使用以下代码:

#ifdef DEBUG 
#define pout cout 
#else
#define pout 0 && cout 
#endif 

使用方法:

pout << "hello world" << endl; 

虽然这可能是一个不错的技巧,但我宁愿不使用它。请记住,多行cout的每一行都必须以pout开头,并且,在预处理之后考虑以下内容:如果(条件)LF //conditional_command(); LF unconditional_command(); - risingballs

1
使用cerr会更加规范。 “cerr”实际上与“cout”类似,但始终刷新输出(顺便说一下,这对调试很有用)。如果您需要删除所有消息,可以通过简单的查找和替换(cerr替换为//cerr)注释掉所有cerr消息。 可能有更好的方法来使用关闭cerr(它写入一个特殊流,错误流,因此得名)。 希望这能帮到你。

0

我在寻找类似的示例并在下面分享我的示例:

#include <iostream>
enum debug_option
{
    DEBUG_DISABLE,
    DEBUG_ENABLE
};

class debug
{
public:
    debug_option debug_state;

    debug() : debug_state(DEBUG_ENABLE) {} // constr
    debug(debug_option state) : debug_state(state) {} // constr

    template<typename T>
    debug & operator<< (T input)
    {
    if (this->debug_state == DEBUG_ENABLE)
        std::cout << input;
    return *this;
    }
};

int main()
{
    debug log, log_lev2(DEBUG_DISABLE);
    log << "print 1..\n" << 55 << " over\n";
    log.debug_state = DEBUG_DISABLE;
    log << "print 2..\n" << 3 << "over\n";
    log_lev2 << "print 3..\n" << 4 << "over\n";
    log_lev2.debug_state = DEBUG_ENABLE;
    log_lev2 << "print 5..\n";
    std::cout << "std::cout << print..\n";
    return 0;
}

欢迎提出更好的建议。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接