问题
I was trying the logger code from this link, but it gives me error. How to implement a good debug/logging feature in a project
#ifndef _LOGGER_HPP_
#define _LOGGER_HPP_
#include <iostream>
#include <sstream>
/* consider adding boost thread id since we'll want to know whose writting and
* won't want to repeat it for every single call */
/* consider adding policy class to allow users to redirect logging to specific
* files via the command line
*/
enum loglevel_e
{logERROR, logWARNING, logINFO, logDEBUG, logDEBUG1, logDEBUG2, logDEBUG3, logDEBUG4};
class logIt
{
public:
logIt(loglevel_e _loglevel = logERROR) {
_buffer << _loglevel << " :"
<< std::string(
_loglevel > logDEBUG
? (_loglevel - logDEBUG) * 4
: 1
, ' ');
}
template <typename T>
logIt & operator<<(T const & value)
{
_buffer << value;
return *this;
}
~logIt()
{
_buffer << std::endl;
// This is atomic according to the POSIX standard
// http://www.gnu.org/s/libc/manual/html_node/Streams-and-Threads.html
std::cerr << _buffer.str();
}
private:
std::ostringstream _buffer;
};
extern loglevel_e loglevel;
#define log(level) \
if (level > loglevel) ; \
else logIt(level)
#endif
More precisely, this #define
is giving errors:
#define log(level) \
if (level > loglevel) ; \
else logIt(level)
The errors are Syntax error: if
and Syntax error: else
But later, I noticed that if I move #include "logger.hpp"
from main.h
to main.cpp
, the problem disappeared. Though 'main.h' is included many times in different places, it does contain '#pragma once'.
Any idea?
回答1:
If loglevel
is known at compile time you can do the following:
template <bool>
struct LogSystem
{
template <class T>
LogSystem& operator << (const T &)
{
//ignore the input
return (*this);
}
};
template <>
struct LogSystem <true>
{
template <class T>
LogSystem& operator << (const T & v)
{
cout << v;
return (*this);
}
};
template <bool B>
LogSystem<B> getLog()
{
return LogSystem<B>();
}
#define log(level) getLog< (level <= loglevel) >()
if loglevel
is not known at compile time:
class iLogSystem
{
public:
virtual iLogSystem& operator << (const int &)
{
//empty
return (*this);
}
virtual iLogSystem& operator << (const custom_type &);
{
return (*this);
}
//make functions for logging all the types you want
};
class LogSystem : public iLogSystem
{
public:
virtual iLogSystem& operator << (const int & v)
{
cout << v;
return (*this);
}
virtual iLogSystem& operator << (const custom_type & q);
{
cout << q.toString();
return (*this);
}
//make functions for logging all the types you want
};
iLogSystem& getLog(const bool l)
{
static LogSystem actual_log;
static iLogSystem empty_log;
if(l)
return &actual_log;
return &empty_log;
}
#define log(level) getLog( level <= loglevel )
回答2:
Any time you want to define a macro that expands to a statement, if the definition contains any compound statements (including if
/else
), you should wrap the definition in do
... while (0)
. The enclosed code will still execute exactly once, and it can be used in any context that requires a statement.
That's the only way I know of to avoid syntax errors when the macro is used within an if
/else
statement, due to the use of semicolons.
So rather that this:
#define log(level) \
if ((level) > loglevel) ; \
else logIt(level)
you can use this:
#define log(level) \
do { \
if ((level) > loglevel) ; \
else logIt(level) \
} while (0)
I've added parentheses around references to the macro's level
parameter, to avoid any possible operator precedence problems. Note also the lack of a semicolon at the end; the semicolon will be supplied by the caller.
On the other hand, an if
/else
can often be replaced by the conditional (ternary) ?:
operator:
#define log(level) \
((level) > loglevel ? 0 : logIt(level))
which allows log(level)
to be used anywhere an expression can be used; that includes statement context if you add a semicolon. You might want to replace 0
by something of the type returned by logIt
; if logIt
is a void function, you might want:
#define log(level) \
((level) > loglevel ? (void)0 : logIt(level))
This all assumes that a macro is the right tool for the job. It's likely that a template (as suggested by this answer) or an inline function will do the job better and with less potential for confusion.
回答3:
Can #define preprocessor directive contain if and else?
Yes.
Regarding your problem: preprocessor is dumb as a rock and performs only simple text subsitution. It is not a function, it is not a language construct, it is simple, dumb text subsitution. As a a result, this:
#define log(level) \
if (level > loglevel) ; \
else logIt(level)
...
log(logINFO) << "foo " << "bar " << "baz";
Turns into this:
if (logINFO > loglevel); // << here's your problem.
else
logIt(logInfo)
<< "foo " << "bar " << "baz";
Your problem is;
. Here, semicolon indicates end of c++ if
statement, so when compiler encounters else
afterwards, it doesn't know what to do with it.
I noticed that if I move #include "logger.hpp" from main.h to main.cpp, the problem disappeared
C++ has "logarithm" function. Which is called log. If your other files use logarithm function, things will get very interesting, because it'll be replaced by your if/else logging code everywhere.
For example, if there's inlined logarithm code somewhere in a header, it'll turn into nonsense if you include your logger
header first. For example log(6.0) + 1
will turn into log (if (6.0 > logLevel); else logIt(6.0)) + 1
, which is not a valid C++ statement.
来源:https://stackoverflow.com/questions/19795038/can-define-preprocessor-directive-contain-if-and-else