Do pre-processor macros for debug log statements have room in C ++?

I recently read Scott Meyers' Effective C ++ Second Edition to improve best practices in C ++. One of its elements encourages C ++ programmers to avoid preprocessor macros and "prefers the compiler." He went so far as to say that there are almost no reasons for macros in C ++, except for #include and # ifdef / # ifndef.

I agree with his reasoning, as you can run the following macro

#define min(a,b) ((a) < (b) ? (a) : (b)) 

with the following C ++ language features

 template<class T> inline const T & min(const T & a, const T & b) { return a < b ? a : b; } 

where inline gives the compiler the ability to remove a function call and insert inline code and a template that can handle several types of data that have an overloaded or inline operator>.

EDIT - . The declaration of this template will not fully match the specified macro if the data types a and b are different. See Pete's Comment for an example.

However, I'm curious to know if using macros to debug logging is a valid use in C ++. If the method below is not good practice, would anyone be kind enough to suggest an alternative way?

I encoded in Objective-C over the past year, and one of my favorite 2D engines (cocos2d) used a macro to create registration statements. The macro is as follows:

 /* * if COCOS2D_DEBUG is not defined, or if it is 0 then * all CCLOGXXX macros will be disabled * * if COCOS2D_DEBUG==1 then: * CCLOG() will be enabled * CCLOGERROR() will be enabled * CCLOGINFO() will be disabled * * if COCOS2D_DEBUG==2 or higher then: * CCLOG() will be enabled * CCLOGERROR() will be enabled * CCLOGINFO() will be enabled */ #define __CCLOGWITHFUNCTION(s, ...) \ NSLog(@"%s : %@",__FUNCTION__,[NSString stringWithFormat:(s), ##__VA_ARGS__]) #define __CCLOG(s, ...) \ NSLog(@"%@",[NSString stringWithFormat:(s), ##__VA_ARGS__]) #if !defined(COCOS2D_DEBUG) || COCOS2D_DEBUG == 0 #define CCLOG(...) do {} while (0) #define CCLOGWARN(...) do {} while (0) #define CCLOGINFO(...) do {} while (0) #elif COCOS2D_DEBUG == 1 #define CCLOG(...) __CCLOG(__VA_ARGS__) #define CCLOGWARN(...) __CCLOGWITHFUNCTION(__VA_ARGS__) #define CCLOGINFO(...) do {} while (0) #elif COCOS2D_DEBUG > 1 #define CCLOG(...) __CCLOG(__VA_ARGS__) #define CCLOGWARN(...) __CCLOGWITHFUNCTION(__VA_ARGS__) #define CCLOGINFO(...) __CCLOG(__VA_ARGS__) #endif // COCOS2D_DEBUG 

This macro provides incredible utility that I want to include in my C ++ programs. Writing a useful log statement is as easy as

 CCLOG(@"Error in x due to y"); 

What's even better is that if COCOS2D_DEBUG is set to 0, then these statements never see the light of day. There is no overhead to verify the conditional statement to see if you should use the logging instructions. This is convenient when moving from development to production. How can I recreate the same effect in C ++?

So, does this type of macro belong to a C ++ program? Is there a better, more C ++ way to do this?

+6
source share
2 answers

First, Scott's statement was made at a time when macros were due to historical reasons. Although this is usually true, there are a few cases where macros make sense. Of these, registration, because only a macro can automatically insert __FILE__ and __LINE__ . In addition, only a macro can solve absolutely nothing (although, based on my experience, this is not a big deal).

Macros like the one you show are not very common in C ++. There are two common options for registering:

 #define LOG( message ) ... << message ... 

which allows you to send messages in the form " x = " << x and can be completely suppressed by overriding the macro and

 #define LOG() logFile( __FILE__, __LINE__ ) 

where logFile returns a wrapper for std::ostream that defines operator<< and allows things like:

 LOG() << "x = " << x; 

We make this method, all expressions to the right of LOG() will always be evaluated, but it will be performed correctly, formatting will not be performed if the log is not active.

+6
source

There are β€œright” things to use macros, and there are bad uses of macros. Using macros where functions work is a bad idea. Using macros where functions DO NOT do the same is fine in my book.

I often use such constructs:

 #defien my_assert(x) do { if (!x) assert_failed(x, #x, __FILE__, __LINE__); } while(0) template<typename T> void assert_failed(T x, const char *x_str, const char *file, int line) { std::cerr << "Assertion failed: " << x_str << "(" << x << ") at " << file << ":" << line << std::endl; std::terminate(); } 

Another trick using the string "operator" is something like the following:

 enum E { a, b, c, d }; struct enum_string { E v; const char *str; }; #define TO_STR(x) { x, #x } enum_string enum_to_str[] = { TO_STR(a), TO_STR(b), TO_STR(c), TO_STR(d), }; 

Keeps quite a few repetitive things ...

So yes, this is useful in some cases.

+2
source

Source: https://habr.com/ru/post/951007/


All Articles