MacroMagic

30 08 2010

So, the past couple of posts have been about error handling, reporting and what-not. There were also mentioned a few macros here and there. To conclude this saga I’d like to make this post about the #define magic; or black magic if you will.

Let’s preface this entire discussion with what I’d like to call, my precondition to the PRECONDITION. I’m not a great proponent of macros. In fact I try to avoid the preprocessor as much as I can [for reasons which are discussed pretty much everywhere concerning C and/or C++ programming]. That said though I don’t actually subscribe to the notion that a #define is evil. Like many things in my development environment the preprocessor is a tool, bit blunt, bit dangerous, but a tool nonetheless. Guns don’t kill people, code-maintainers do; in frustration. But let’s get back to the macro faeries.

The first iteration of my macro implementation went fairly close to the normal assert. For two very good references about asserting I’d like to point to the following, Game Programming Gems and http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/ which doesn’t seem to show in my browser so I don’t know if it’s still there or not?

Ok, enough talking, let’s code; this is what the #define looked in practice.
PRECONDITION(mCreated==true, "window has not been created yet");

Seems alright on the surface but the first question I’ve had for it was whether the condition should be met or not. That is, am I saying:
if (condition) { report message; }or
if (not condition) { report message; }

One might be inclined to simply look at the PRECONDITION code and derive conclusions from there but I think functionality should be understandable through use of code, not inspection [which is why I haven’t posted up the code yet]; so I’ve changed the code to look like this;
PRECONDITION(mCreated==false,/*otherwise*/"window has not been created yet");

Better in the sense that it now clarifies the intent but it still rubs me the wrong way since I’ve had to resort to a comment [which is external to code]. In response I’ve done someting which I’m still not sure is a good idea but I’m keeping at the moment. I’ve separated the PRECONDITION macro into two interrelated and interconnected macros;
PRECONDITION(mCreated==true) OTHERWISE_REPORT("this window has not been created yet");

It’s not an ideal solution, in fact it’s blunt and ugly [although ugly here is good since it tells me it’s a macro], but I’m satisfied with it at the moment. The main reason I have for using a macro rather than a function call is the need to report file, function and line location of the call as well as provide a breakpoint into the code at the appropriate place for a trace.

The Postcondition, as you may have already guessed looks pretty much the same. I value code coherency very much, if I write one type of interface I keep the rest of the related types with the same interface.
POSTCONDITION(mCreated==false) OTHERWISE_THROW("could not create window");

And as for the actual PRECONDITION and POSTCONDITION code;
#ifndef _FORGE_ERROR_POSTCONDITION_
#define _FORGE_ERROR_POSTCONDITION_

#include <sstream>
#include <exception>

#define POSTCONDITION(condition)  \
do        \
{ \
  if (!(condition)) \
  { \
    std::ostringstream os; \
    os <<'\n'<<"** POSTCONDITION FAILED **"\
       <<'\n'<<"CONDITION : "<<#condition \
       <<'\n'<<"FILE : "<<__FILE__ \
       <<'\n'<<"LINE : "<<__LINE__ \
       <<'\n'<<"FUNCTION : "<<__FUNCTION__
#define OTHERWISE_THROW(name, message) \
       <<'\n'<<"NAME : "<<name \
       <<'\n'<<"MESSAGE : "<<message<<'\n'; \
       throw std::range_error(os.str().c_str()); \
  } \
}while(0)

#endif/*_FORGE_ERROR_POSTCONDITION_*/

#ifndef _FORGE_ERROR_PRECONDITION_
#define _FORGE_ERROR_PRECONDITION_

#include <iostream>

#ifndef UNIT_TEST_MODE
  #define BREAKPOINT_AND_ABORT __debugbreak(); std::abort()
#else
  #define BREAKPOINT_AND_ABORT
#endif/*UNIT_TEST_MODE*/

#ifndef DISABLE_FORGE_ASSERT
#define PRECONDITION(condition)        \
do                \
{ \
  if (!(condition)) \
  { \
    std::clog \
    <<'\n'<<"** PRECONDITION FAILED **" \
    <<'\n'<<"CONDITION: "<<#condition \
    <<'\n'<<"FILE : "<<__FILE__ \
    <<'\n'<<"LINE : "<<__LINE__ \
    <<'\n'<<"FUNCTION : "<<__FUNCTION__
#define OTHERWISE_REPORT(name, message) \
    <<'\n'<<"NAME : "<<name \
    <<'\n'<<"MESSAGE : "<<message \
    <<'\n'; \
    BREAKPOINT_AND_ABORT; \
  } \
}while(0)
#else
#define PRECONDITION(condition) do{ sizeof(condition);
#define OTHERWISE_REPORT(message) }while(0)

#endif/*DISABLE_FORGE_ASSERT*/

#endif/*_FORGE_ERROR_PRECONDITION_*/

Bit of a hack, but I’ve done worst. I’ve also been toying around with trying to move the PRECONDITION assert to compile time, or at least link time by using templates. I’ve not had much success yet, but I like templates so I’ll see if I can come up with something. Anyways, dishes await. Strange, seems like every time I need to do the dishes I try to post something before, coincidence? or black magic??

Hey Joe, where you goin’ with that gun in your hand?”

Advertisements

Actions

Information

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




%d bloggers like this: