Removing code at compile time with preprocessors

I am making a logging class which (very stripped down) has an interface something like this:
1
2
3
4
5
6
7
8
9
10
class Log {
public:
   Log();
   ~Log();

   void AddStream(std::ostream&);
   void AddStream(std::string filename);

   void Write(std::string);
};


Now, what I want to do is allow the user of this class to simply define a macro: NO_LOG which will remove all the log code so that logging can be disabled at compile time like this:
void Log::Log() {
#ifndef NO_LOG
// blah
// blah
#endif
}

The problem is, many of my functions are too long to inline so I have to put the code in CPP files. Thus if a user is using the library from a static or dynamic library, the code will be precompiled. Thus, how can I implement the removal of code with macros, if at all?

Thanks in advance for any help.
Like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// File: Log.h
#ifndef LOG_H
#define LOG_H

#ifdef NO_LOG

class NoLog {
    public:
        inline NoLog() {}
        inline ~NoLog() {}
        inline void AddStream( std::ostream& ) {}
        inline void AddStream( const std::string& ) {}
        inline void Write( const std::string& ) {}
};

typedef NoLog Log;
#else
class DoLog {
    public:
        DoLog();
        ~DoLog();
        void AddStream( std::ostream& );
        void AddStream( const std::string& );
        void Write( const std::string& );
};

typedef DoLog Log;
#endif

#endif 


This way you don't penalize your users with extraneous calls to empty functions when logging is turned off.
Great - that should definitely fix my problem. Thanks for the help :)

PS: Out of a matter of interest, how serious an issue is the empty function call thing? I had previously wondered about this, but when I ran some tests, the MSVC compiler seemed to happily optimise out all the calls to empty functions. Would other compilers not do such a good job, or is it just generally bad practise to entrust such a task to the compiler's discretion?
You can trust that your compiler will optimize out a call to an empty function provided that the compiler can see the implementation of the function at the call point. This is why I provided the body of the empty functions in the header. If you were to move the bodies to a .cpp file, you'd lose that optimization.
Right. Well thanks so much for the advice - it has helped me a lot!
EDIT: Also, given that only one half of that code will ever get compiled, why not just call them both Log and forget the typedef?

EDIT EDIT: Ah no, I've tried it now and I realise and understand that this causes multiply defined symbol linker errors. Thanks again.
Last edited on
Elegant solution from jsmith (I wouldn't have thought of some details), but I have to mention something.

Using define before including a header file to parametrize its content is not a very good affair. If header A.h depends on B.h, and includes B.h with certain defines present at the time, and then some source file includes A.h and then B.h with other defines present at that time, the result is that the latter setup is ignored, because the multiple inclusion protection of B.h kicks in.

This means that in all cases you will have to make the defines global for the entire project - by feeding them from the command line, or with implicit header file (I think that gcc supports this ). And if the options are global, then they will influence the implementation files (.cpp) as well as the header files, and there is no problem with changing the method definitions in the implementation files directly.

If you want to keep the possibility to use different setups for different sources and still allow multiple inclusion, you can either have policy that the option should be only controlled at the start of the implementation files, or do smth like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// File: Log.h
#ifndef LOG_H
#define LOG_H

class NoLog {
    public:
        inline NoLog() {}
        inline ~NoLog() {}
        inline void AddStream( std::ostream& ) {}
        inline void AddStream( const std::string& ) {}
        inline void Write( const std::string& ) {}
};
class DoLog {
    public:
        DoLog();
        ~DoLog();
        void AddStream( std::ostream& );
        void AddStream( const std::string& );
        void Write( const std::string& );
};
#endif

#ifdef NO_LOG

#define Log NoLog
#else

#define Log DoLog
#endif 
Not a very nice solution, because Log becomes a preprocessor macro. Alternatively, you can try not to rely on the preprocessor and expose template class to the user instead.

Regards

EDIT: Bummer - one endif went at the wrong place.
Last edited on
Ok then. What if I do it with template specialization like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/******************** the header file ***********************/
#include <iostream>
#include <vector>

template <int n = 1> class Log {
	class OStream { /* etc */ };
	int priority;
	std::vector<OStream> streams;
	// etc

public:
	Log(bool forcewait, bool timestamp, int priority);
	~Log();

	void AddStream(std::ostream&);
	void AddStream(const std::string&);
	void Write(const std::string);
	// etc
};

template <> class Log<0> {
public:
	inline Log(bool forcewait, bool timestamp, int priority) {}
	inline ~Log() {}

	inline void AddStream(std::ostream&) {}
	inline void AddStream(const std::string&) {}
	inline void Write(const std::string&) {}
	// etc
};

/****************** the file using the log ************************/
int main()
{
	Log<> log(true, true, 0);
	log.Write("Foo");
	log.Write("Bar");
	return 0;
}


Then I could disable the logging merely by changing Log<> log to Log<0> log.

I have tried this and it seems to work. Will it result in zero performance impact if the log is disabled, like the other one, or will there still be some overhead?
Well, it really wouldn't be any different from having a DoLog and a NoLog class since Log<0> and Log<1> are different types...
Yes, it would. I didn't think that one through so much...

@simeonz
expose template class to the user instead

I'm sorry I don't understand. What do you mean by this?
I imagined exactly what you did. I thought, if someone wants to override the global setting, they could do
1
2
3
4
5
#define LOGGING 0
//or
#define LOGGING 1
//..any code, header includes, etc.
#undef LOGGING 
and then use Log<LOGGING>.method();

However, it is true that they could simply have
1
2
3
4
5
#define Log NoLog
//or
#define Log DoLog
//..any code, header includes, etc.
#undef Log 

I confess, the usability of the second solution improves over the first one, but I am not comfortable with using macros for types. My main point was not to "set" the type when the header file is included, but to allow the user of the header file to set it. Your logging header will define the two alternatives (with templates or not), and let the user choose. Whether the user inherits the global setting or overrides it is not the header file's business. And you will not violate the one definition rule.

Note also, that this is probably overkill for simple logging. But there are general consequences from using this pattern. They are that you can not control the options locally. And since you have no way to make sure whether someone has tried to control the option locally or not, it is theoretically a little unsafe. I thought I should mention it, in case you ever decide to use it for say allocation modes. Whether you want to control the option for logging locally is entirely different story. You probably don't.

Regards
By "control locally", do you mean change the logging behaviour in the middle of some arbitrary function?

If so, then this is handled separately by my class Log::Priority. Priorities are processed at runtime, and so can be used to control logging behaviour mid program at runtime.

However, I also wanted to have to global compile time control which we've been discussing in this thread. The reason I want this feature is this: suppose I add many logging calls and later decide they are slowing down the program too much (however unlikely this situation is!). In such a circumstance, I should like to have the option of removing all logging code with a single code change, rather than having to remove all the logging function calls.

EDIT: I can see the merits of using the templates, and the global LOGGING macro.
the usability of the second solution improves over the first one

Could you explain the advantages of the second one? At the moment, the first one looks entirely better to me (other than the extra effort of having to write all the 'template' bits in front of function implementations...).
Last edited on
Topic archived. No new replies allowed.