在“测试模式”下打印信息,但不在“正常执行”中打印。

6

我正在使用一个C++应用程序,它使用特殊的dprintf函数来打印信息,这是一个示例:

dprintf(verbose, "The value is: %d", i);

当我为了测试目的定义详细模式时,我会打印信息,而在正常执行时,我不定义它,也不会在屏幕上看到无用的信息。我的问题是如何实现这个功能或者实现同样的想法?

3个回答

16

我尽量避免使用可变参数的C风格函数,主要有两个原因:

  • 它们不安全且无法使用运算符<<
  • 它们无法识别提供的参数是否过少或过多

我已经想出了一种使用boost::fusion的方法,以类型安全的方式给出参数。它会迭代这些参数,在遇到%时打印它们。如果提供太少或太多的参数,将抛出异常。

但是还有一个问题:可变宏在C++中还不是标准。因此,我制作了两个版本。其中一个适用于当前的C++版本,你需要通过以下方式调用它:

dprintf("name: %, value: %\n", ("foo", 42));

那么,使用可变参数宏的另一种版本,可以通过定义一个预处理器符号来使用,这使你能够编写如下代码:

dprintf("name: %, value: %\n", "foo", 42);

这是代码。 boost.fusion提供了更多关于此的详细信息:

#include <boost/fusion/include/sequence.hpp>
#include <boost/fusion/include/make_vector.hpp>
#include <boost/fusion/include/next.hpp>
#include <stdexcept>
#include <iostream>

template<typename IterS, typename IterSeqE>
void print_vec(IterS b, IterS e, IterSeqE, IterSeqE) {
    while(b != e) {
        if(*b == '%') {
            if(++b != e && *b == '%') {
                std::cout << '%';
            } else {
                throw std::invalid_argument("too many '%'");
            }
        } else {
            std::cout << *b;
        }
        ++b;
    }
}

template<typename IterS, typename IterSeqB, typename IterSeqE>
void print_vec(IterS b, IterS e, IterSeqB seqb, IterSeqE seqe) {
    while(b != e) {
        if(*b == '%') {
            if(++b != e && *b == '%') {
                std::cout << '%';
            } else {
                std::cout << *seqb;
                return print_vec(b, e, next(seqb), seqe);
            }
        } else {
            std::cout << *b;
        }
        ++b;
    }
    throw std::invalid_argument("too few '%'");
}

template<typename Seq>
void print_vec(std::string const& msg, Seq const& seq) {
    print_vec(msg.begin(), msg.end(), begin(seq), end(seq));
}

#ifdef USE_VARIADIC_MACRO
#  ifdef DEBUG
#    define dprintf(format, ...) \
         print_vec(format, boost::fusion::make_vector(__VA_ARGS__))
#  else 
#    define dprintf(format, ...)
#  endif
#else
#  ifdef DEBUG
#    define dprintf(format, args) \
         print_vec(format, boost::fusion::make_vector args)
#  else 
#    define dprintf(format, args)
#  endif
#endif

// test, using the compatible version. 
int main() {
    dprintf("hello %, i'm % years old\n", ("litb", 22));
}

1
我不能代表其他编译器,但对于GCC来说,-Wformat和__attribute__((format))是你的好朋友。这允许使用printf风格的API,并具有operator<<的所有类型检查优点。 - Tom
1
是的,我知道那些属性。但它不允许你传递自己的类型。例如,你可能使用一个列表,并想要为你的列表重载operator<<。使用printf时,你需要再次编写相同的循环,并且受到printf理解的特定元素类型的限制。 - Johannes Schaub - litb
重载运算符<< 允许将打印代码放在您想要打印的类型的一侧,这比将打印代码放在我的调试代码一侧更好。因此,每当您有可打印的内容时,您的类型安全的 dprintf 解决方案已经理解它。 - Johannes Schaub - litb
C++相对于C语言的一个重要改进就是可以将代码分离为不同的模块,并在库之间共享代码。如果一个类型是可打印的,那么它会重载operator<<,这样每个人都可以使用它。我非常喜欢这一点。 - Johannes Schaub - litb
2
我同意任何对象都可以被打印的说法,但我发现某些对象需要用2-3种不同的模式进行“序列化”。二进制序列化用于存储,基本打印以及调试转储。ostream<<机制假定一个对象只有单一的序列化机制。 - Tom
1
因此,我最终使用实现分离的例程(将ostream&作为参数)而不是使用ostream<<。 - Tom

9
#ifdef DEBUG
#define dprintf(format, ...) real_dprintf(format, __VA_ARGS__)
#else
#define dprintf
#endif

这里的real_dprintf()是被调用的“真实”函数,而dprintf()只是一个包装调用的宏。


我本来也要发同样的答案,所以我保存了它并点赞你的。这是我在C++代码中使用预处理器宏的唯一情况。 - Daniel Daranas

4

预处理器方案是可行的,但每次从一种方式转换到另一种方式都需要重新构建,这可能会让人感到烦恼。我通常会在运行时做出决定。首先,我声明:

static void do_nothing(const char *fmt, ...) { (void)fmt; }
extern void real_dprintf(const char *fmt, ...);
void (*dprintf)(const char *fmt, ...) = do_nothing;

然后在初始化代码中,我有以下内容:

if (getenv("APPLICATION") && strstr(getenv("APPLICATION"), "dprintf"))
    dprintf = real_dprintf;

这样我就可以通过改变环境变量的值来快速更改模式。


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