基于预处理器的名称空间限定函数调用的排除

时间:2010-08-11 08:46:41

标签: c++ namespaces c-preprocessor

我目前正在将报告库作为大型项目的一部分。它包含一组日志记录和系统消息功能。我正在尝试利用预处理器宏去除严格用于调试的函数调用的子集,以及函数定义和实现本身,使用条件编译和函数(如宏定义为无)(类似于{{的方式)如果定义了assert(),则会删除1}}调用。我遇到了问题。我更喜欢完全限定命名空间,我发现它提高了可读性;我将报告函数包装在命名空间中。因为冒号字符不能是宏令牌的一部分,所以我无法在剥离函数调用时包含命名空间。如果我单独定义函数,我最终会使用DEBUG。我考虑过只使用条件编译来阻止这些函数的函数代码,但我担心编译器可能无法正确地优化空函数。

Namespace::

有关如何使用预处理器处理命名空间限定符的任何想法吗? 关于,问题,只是删除功能代码的想法?
还有其他方法可以实现我的目标吗?

4 个答案:

答案 0 :(得分:2)

这是我几个月前写一个类似的图书馆时的表现。是的,您的优化器将删除空的内联函数调用。如果将它们声明为脱节(不在头文件中),除非使用LTO,否则编译器不会内联它们。

namespace Reporting
{
    const extern std::string logFileName;

    void Report(std::string msg);
    void Report(std::string msg, std::string msgLogAdd);
    void Log(std::string msg);
    void Message(std::string msg);

    #ifdef DEBUG
        inline void Debug_Log(std::string message) { return Log(message); }
        inline void Debug_Message(std::string message) { return Message(message); }
        inline void Debug_Report(std::string message) { return Report(message); }
        inline void Debug_Assert(bool test, std::string message) { /* Not sure what to do here */ }
    #else
        inline void Debug_Log(std::string) {}
        inline void Debug_Message(std::string) {}
        inline void Debug_Report(std::string) {}
        inline void Debug_Assert(std::string) {}
    #endif
};

就像旁注一样,除非你需要制作副本,否则不要按值传递字符串。请改用const引用。它可以防止对每个函数调用的字符串进行昂贵的分配+ strcpy。

编辑:实际上,现在我考虑一下,只需使用const char *。看一下装配体,它的速度要快得多,特别是对于空功能体。

海湾合作委员会在-O1优化了这一点,我不认为这有很多问题:

clark@clark-laptop /tmp $ cat t.cpp
#include <cstdio>

inline void do_nothing()
{
}

int main()
{
        do_nothing();
        return 0;
}
clark@clark-laptop /tmp $ g++ -O1 -S t.cpp   
clark@clark-laptop /tmp $ cat t.s
        .file   "t.cpp"
        .text
.globl main
        .type   main, @function
main:
.LFB32:
        .cfi_startproc
        movl    $0, %eax
        ret
        .cfi_endproc
.LFE32:
        .size   main, .-main
        .ident  "GCC: (Gentoo 4.5.0 p1.2, pie-0.4.5) 4.5.0"
        .section        .note.GNU-stack,"",@progbits

经过一些调整后,如果你使用const char *,not std :: string或const std :: string&amp;,它似乎只会被完全删除。这是const char *的组件:

clark@clark-laptop /tmp $ cat t.cpp 
inline void do_nothing(const char*)
{
}

int main()
{
        do_nothing("test");
        return 0;
}
clark@clark-laptop /tmp $ g++ -O1 -S t.cpp 
clark@clark-laptop /tmp $ cat t.s
        .file   "t.cpp"
        .text
.globl main
        .type   main, @function
main:
.LFB1:
        .cfi_startproc
        movl    $0, %eax
        ret
        .cfi_endproc
.LFE1:
        .size   main, .-main
        .ident  "GCC: (Gentoo 4.5.0 p1.2, pie-0.4.5) 4.5.0"
        .section        .note.GNU-stack,"",@progbits

这里有const std :: string&amp; ...

        .file   "t.cpp"
        .section        .rodata.str1.1,"aMS",@progbits,1
.LC0:
        .string "test"
        .text
.globl main
        .type   main, @function
main:
.LFB591:
        .cfi_startproc
        subq    $24, %rsp
        .cfi_def_cfa_offset 32
        leaq    14(%rsp), %rdx
        movq    %rsp, %rdi
        movl    $.LC0, %esi
        call    _ZNSsC1EPKcRKSaIcE
        movq    (%rsp), %rdi
        subq    $24, %rdi
        cmpq    $_ZNSs4_Rep20_S_empty_rep_storageE, %rdi
        je      .L11
        movl    $_ZL22__gthrw_pthread_cancelm, %eax
        testq   %rax, %rax
        je      .L3
        movl    $-1, %eax
        lock xaddl      %eax, 16(%rdi)
        jmp     .L4
.L3:
        movl    16(%rdi), %eax
        leal    -1(%rax), %edx
        movl    %edx, 16(%rdi)
.L4:
        testl   %eax, %eax
        jg      .L11
        leaq    15(%rsp), %rsi
        call    _ZNSs4_Rep10_M_destroyERKSaIcE
.L11:
        movl    $0, %eax
        addq    $24, %rsp
        .cfi_def_cfa_offset 8
        ret
        .cfi_endproc
.LFE591:
        .size   main, .-main
        [Useless stuff removed...]
        .ident  "GCC: (Gentoo 4.5.0 p1.2, pie-0.4.5) 4.5.0"
        .section        .note.GNU-stack,"",@progbits

巨大的差异,嗯?

答案 1 :(得分:0)

我不确定我是否完全理解你的问题。请问有以下帮助吗?

namespace X
{
    namespace{int dummy;}
    void debug_check(int);
}

#ifdef DEBUG
#define DEBUG_CHECK(ARG) debug_check(ARG)
#else 
#define DEBUG_CHECK(ARG) dummy // just ignore args
#endif

int main()
{
 X::DEBUG_CHECK(1);
}

此解决方案可能无效,因为它可以生成“无效声明”警告。一个可能更好的解决方案是在函数声明中吞噬名称空间前缀:

// debug_check and "#ifdef DEBUG" part omitted
namespace X
{
    typedef void dummy_type; 
}
namespace Y
{
    typedef void dummy_type; 
}
typedef void dummy_type;

#define DEBUG(X) dummy_type dummy_fn();

int main()
{
    X::DEBUG(1);
    Y::DEBUG(2);
    X::DEBUG(3);
    Y::DEBUG(4);    
    DEBUG(5);   
    DEBUG(6);   
};

只要dummy_type的任何定义产生相同的类型,这应该是合法的,因为typedef不是不同的类型。

答案 2 :(得分:0)

您可以将您的日志记录功能替换为什么都不做的功能,不是吗?

答案 3 :(得分:0)

我知道这些问题已经应答了很久,但是当我将日志宏放入命名空间时,我遇到了这个问题。您建议使用空函数和优化级别。克拉克盖布尔斯让我思考,因为使用const char*const std::string&的结果不同。以下代码在没有启用优化级别的情况下没有对程序集进行任何合理更改:

#include <iostream>
#undef _DEBUG // undefine to use __NOJOB

namespace Debug
{
    typedef void __NOJOB;

    class Logger
    {
    public:
        static void Log( const char* msg, const char* file, int line )
        {
            std::cout << "Log: " << msg << " in " <<
                file << ":" << line << std::endl;
        }
    };
}

#ifdef _DEBUG
#define Log( msg ) Logger::Log( msg, __FILE__, __LINE__ );
#else
#define Log( msg )__NOJOB(0);
#endif

int main()
{
   Debug::Log( "please skip me" );

   return 0;
}

http://assembly.ynh.io/创建了程序集:

main:
                .LFB972:
                    .cfi_startproc
0000 55             pushq   %rbp
                    .cfi_def_cfa_offset 16
                    .cfi_offset 6, -16
0001 4889E5         movq    %rsp, %rbp
                    .cfi_def_cfa_register 6 // <- stack main
// no code for void( 0 )  here
0004 B8000000       movl    $0, %eax // return
     00
0009 5D             popq    %rbp // -> end stack main
                    .cfi_def_cfa 7, 8
000a C3             ret

也许我弄错了或者弄错了什么?很高兴听到你的消息。