Matthew R
Matthew R

Reputation: 232

Preprocessor based exclusion of namespace qualified function calls

I’m currently working on a reporting library as part of a large project. It contains a collection of logging and system message functions. I’m trying to utilize preprocessor macros to strip out a subset of the functions calls that are intended strictly for debugging, and the function definitions and implementations themselves, using conditional compilation and function like macros defined to nothing (similar to the way that assert() calls are removed if DEBUG is defined). I’m running into a problem. I prefer to fully qualify namespaces, I find it improves readability; and I have my reporting functions wrapped in a namespace. Because the colon character can’t be part of a macro token I am unable to include the namespace in the stripping of the function calls. If I defined the functions alone to nothing I end up with Namespace::. I've considered just using conditional compilation to block the function code for those functions, but I am worried that the compiler might not competently optimize out the empty functions.

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
        void Debug_Log(std::string message);
        void Debug_Message(std::string message);
        void Debug_Report(std::string message);
        void Debug_Assert(bool test, std::string message);
    #else
        #define Debug_Log(x);
        #define Debug_Message(x);
        #define Debug_Report(x);
        #define Debug_Assert(x);
    #endif

};

Any idea on how to deal with the namespace qualifiers with the preprocessor?
Thoughts on, problems with, just removing the function code?
Any other ways to accomplish my goal?

Upvotes: 3

Views: 1951

Answers (4)

lifeOfPI
lifeOfPI

Reputation: 318

I know that this questions has been answered since ages, but I came across this problem when I put a log macro into a namespace. You were suggesting empty functions and optimization levels. Clark Gaebles made me think, because of the different results using const char*or const std::string&. The following code gives me no reasonable changes in assembly with no optimization levels enabled:

#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;
}

created assembly by 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

Maybe I made an mistake or understood something wrong? Would be nice to hearing from you.

Upvotes: 0

Clark Gaebel
Clark Gaebel

Reputation: 17948

This is how I did it when I wrote a similar library several months back. And yes, your optimizer will remove empty, inline function calls. If you declare them out-of-line (not in the header file), your compiler will NOT inline them unless you use 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
};

Just as a side note, don't pass strings by value unless you need to make a copy anyways. Use a const reference instead. It prevents an expensive allocation + strcpy on the string for EVERY function call.

EDIT: Actually, now that I think about it, just use a const char*. Looking at the assembly, it's a LOT faster, especially for empty function bodies.

GCC optimizes this out at -O1, I don't think there's much of an issue with this:

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

After a bit of tweaking, it seems that this will only be a FULL removal if you use const char*, NOT std::string or const std::string&. Here's the assembly for the 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

And here's with const std::string&...

        .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

Huge difference, eh?

Upvotes: 2

Jens Gustedt
Jens Gustedt

Reputation: 78923

you could just have your logging function replaced by a function that does nothing, no?

Upvotes: 0

Nordic Mainframe
Nordic Mainframe

Reputation: 28747

I am not sure if I fully understand your problem. Would the following help?

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);
}

This solution might not work, because it can generate a "statement without effect" warning. A potentially better solution would be to gobble the namespace prefix up in a function declaration:

// 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);   
};

As long as any definition of dummy_type yields the same type, this should legal, because typedefs are not distinct types.

Upvotes: 0

Related Questions