Tuesday, 29 September 2009

Making Error Codes Mandatory

Lately, I've been looking over some code I re-factored a while ago while doing a monumental code merge: I'd replaced a load of functions that had boolean returns and error strings returned by reference argument with simple integer error code returns. (If you think this is wrong, then you must be one of the people writing hard coded error strings into core libraries causing your app designers and document writers headaches with the spelling mistakes it takes them an age to remove and preventing them from localising your apps or having different strings in different deployments).

Anyway, back to the main point: I considered using mandatory error codes at the time so clients couldn't ignore them. There's a Dr. Dobbs article on this too, that goes along similar lines, but I wanted to make an important extension to the logical design.

The problem with the idea of the mandatory error code is that it throws on destruction if it hasn't been "queried" (had the embedded error code extracted), which in exception safety terms is the worst thing we can do! So, the thing we have to do is make sure they don't live (long) on the stack. The way I addressed this is to make them non-copyable, so they can construct from their embedded type only:

class mandatory_error_code
mandatory_error_code( const T & i_value )
: m_value( i_value )
, m_throw( true )
if( m_throw )
throw std::logic_error( "un-handled error code" );
operator const T() const
m_throw = false;
return m_value;
mandatory_error_code( const mandatory_error_code & );
mandatory_error_code& operator=( const mandatory_error_code & );
const T m_value;
mutable bool m_throw;

There are two nice advantages to this approach:
  1. The only place you can write mandatory_error_code<> is as a return argument and it must be constructed from a convertible type. This makes porting code really easy, as all you do is change your function signatures.
  2. Client code that ignores error codes by not copying a value or comparing it to a convertible immediately start throwing on the line where the function that returns the error code is called. It is impossible to make the class live longer and risk run time termination if a different exception is thrown.
The original Dr. Dobbs article on this has a non-throwing error code class that wraps the value, but I'm not so sure this is now required: Why wrap an error code in another class when the other class does nothing? If you want to capture the error code for later comparison, just use the "original" type and sacrifice perhaps a tiny bit of readability for a whole extra class. There is however still a way to compromise your run time and that is by throwing from the assignment operator of the embedded type. Despite not being protected by design here, we are most likely protected by circumstance: if the assignment operator throws, it will throw on the construction of mandatory_error_code, so the destructor will never be reached.

There is another "Modern C++" idea that you could use to extend this class, and that is to replace the hard coded throw with a policy (as a template argument), but until I need more than one policy, I'll assume "YAGNI" and leave it as it is.

No comments:

Post a Comment