The other day I was updating a logging system. For those that don't know, a logging system (or logger) is usually a system created along side a program or engine that allows programmers to log messages, or errors, and to stream their output in different ways, like to a debug console, or to a file, or even an email in some rare cases (like a critical program failing in a bad way and a live product team needs to know immediately). Anyway, I was updating a logging system, and I needed two things that it did not yet offer:
- I needed the parameters being logged to have no expense within the shipped product (the product that was in the customer's hands).
- Without macros there are ways people try to limit logging commands:
- Multiple function definitions for your logging commands, the shipped version of these do nothing when called.
#define SHIPPING_BUILD #include <iostream> namespace logging { #ifndef SHIPPING_BUILD void print( int i ) { std::cout << i; } #else void print ( int i ) { } #endif } int expensiveFcnReturningAnInt() { // The compiler would actually optimize this so it would // be extremely cheap to call, but you get the idea, this // function would be something you don't want to happen // in a shipping build if used only for logging. return 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1; } int main(int argc, const char * argv[]) { logging::print( expensiveFcnReturningAnInt() ); return 0; }
- In the above example the expense of the logging itself is removed, there is nothing logged to the debug console, or written to a log file. But the main issue here, is that expensiveFcnReturningAnInt() was still called, which is expensive, completely unnecessary, and useless
- With macros you can affect the code at compile time. In this example the logging statement will no longer even exist in the program for shipping builds.
-
#define SHIPPING_BUILD #include <iostream> #ifndef SHIPPING_BUILD #define INTERNAL_PRINT(x) logging::print(x) #else #define INTERNAL_PRINT(x) ((void)0) #endif namespace logging { void print( int i ) { std::cout << i; } } int expensiveFcnReturningAnInt() { // The compiler would actually optimize this so it would // be extremely cheap to call, but you get the idea, this // function would be something you don't want to happen // in a shipping build if used only for logging. return 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1; } int main(int argc, const char * argv[]) { INTERNAL_PRINT( expensiveFcnReturningAnInt() ); return 0; }
- In this sample, the call inside of main() to INTERNAL_PRINT is completely optimized out of the program when SHIPPING_BUILD is defined.
- I needed a simple, one-line logging command that accepted a stream of information, just like an std::stringstream.
- Without a macro, you would be forced to make an entire class to try and get you close to this functionality, and even then you might not get it.
- With a macro you can have exactly what you want:
#define SHIPPING_BUILD #include <iostream> #ifndef SHIPPING_BUILD #include <sstream> #define PRINT_STREAM(x) \ { \ std::stringstream strStream; \ strStream << x; \ logging::print(strStream.str().c_str()); \ } #else #define PRINT_STREAM(x) ((void)0) #endif namespace logging { void print( const char* message ) { std::cout << message; } } int expensiveFcnReturningAnInt() { // The compiler would actually optimize this so it would // be extremely cheap to call, but you get the idea, this // function would be something you don't want to happen // in a shipping build if used only for logging. return 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1; } int main(int argc, const char * argv[]) { PRINT_STREAM( "Expensive fcn result is: " << expensiveFcnReturningAnInt() ); return 0; }
No comments:
Post a Comment