Mandatory Error Codes Revisited

Mandatory error codes force callers of a method to accept and use the returned error code.


August 02, 2006
URL:http://www.drdobbs.com/cpp/mandatory-error-codes-revisited/191601612

Guy is chief architect of event processing for the Amdocs Billing Platform (http://www.amdocs.com). He can be contacted at [email protected].


In his article "Three Ideas" (http://www.ddj.com/dept/cpp/184401917), Andrei Alexandrescu described a framework for "mandatory error codes" that forces callers of a method to accept and use the returned error code. In this article, I refer to these codes as "forcing methods."

The idea is simple, yet powerful: A template class ErrorCode, which holds the real error code, is returned from a forcing method. When the returned ErrorCode goes out of scope, it throws an exception—unless the caller uses the error code (using the cast operator). The caller can explicitly ignore the error code by casting the ErrorCode with IgnoreError. Listing One presents the classes that make up this framework, while Example 1 shows how you can use it.



struct IgnoreError {};

template <class T>
class ErrorCode
{
    mutable bool    read_;
    T           code_;
public:
    ErrorCode(const T& code):
read_(false), code_(code) {
}
ErrorCode(const ErrorCode& rhs):
read_(rhs.read_), code_(rhs.code_) {
    rhs.read_ = true;
}
operator T() {
    // disarm exception
    this->read_ = true;
    return this->code_;
}
    operator IgnoreError()  {
    // disarm exception
    this->read_ = true;
    return IgnoreError();
}
~ErrorCode() {
    if (!this->read_)
    {
        throw MandatoryErrorCodeException(....);
    }
}
};
Listing One

ErrorCode<int> FallableFunction();	// function declaration
ErrorCode<int> result = FallableFunction();	// ok
if (FallableFunction ()) {...}	// ok
FallableFunction();	// will throw
(IgnoreError) FallableFunction();	// ok

Example 1: Using the framework.

However, the problem with this framework is the exception that might be thrown at the destructor of ErrorCode. For instance, Example 2 looks perfectly normal. You don't ignore the returned error codes; you accept and use them. There's even a special recover method that can handle everything. Well, almost everything.

void AClass::someMethod() 
{
   try {
      ErrorCode<int> ec1 = this->fallableMethod_1();
      ErrorCode<int> ec2 = this->fallableMethod_2();
      if (ec1 && ec2) {
	  this->doSomething();
      }
   }
   catch (...) {
      this->recover();
   }
}

Example 2: Problem code.

Here's the problem: If this->fallableMethod_2() throws, then the ec1 destructor is activated. But since no one has yet used ec1, another exception is thrown (from activating ec1's destructor), which terminates the process (set_terminate is called but this won't help). This is a big surprise if you've followed all the rules.

The Solution

If you want to use this kind of framework effectively, you first need to tweak it a bit.

First, change your assumption that an ErrorCode must be used. There is no real way to make sure that users actually used it. You should be more concerned with callers accepting the return code, rather than having them use it. After all, a caller to this kind of method can accept the error code, then assign it to some variable that is not used.

Second, make sure that implicitly thrown exceptions won't terminate the process. To do this, first add a new class, ThrowableErrorCode (Listing Two). This class is returned from methods that want to enforce error code checking, instead of the ErrorCode. ThrowableErrorCode doesn't grant access to the error code it carries.

template <class CODE>
class ThrowableErrorCode

{
    template <class CODE> friend class ErrorCode;
    CODE        code_;
    bool        throw_;
public:
    /// ctor - receive the code and arm the exception
    ThrowableErrorCode(CODE i_code):
    code_(i_code),
    throw_(true)
    {}
    // explicitly ignore the error code and avoid exception
    operator IgnoreError() {
        this-> throw_ = false;
        return IgnoreError();
    }
    ~ThrowableErrorCode() {
        // will throw unless ErrorCode<CODE> or
                // IgnoreError will prevent it
        if (this-> throw_)
        {
            throw MandatoryErrorCodeException(...);
        }
    }
};
Listing Two

As its name implies, ThrowableErrorCode might throw an exception. In fact, it throws one when it goes out of scope. The only way to stop it from doing so is by either explicitly ignoring the error code using ThrowableErrorCode's operator IgnoreError, or by assigning it into an ErrorCode.

The new ErrorCode (Listing Three) does not throw an exception. It disarms ThrowableErrorCode and grants access to the error code.

template <class CODE>
class ErrorCode {
    CODE    code_;
public:
    // Explicit ctor to make sure that the user of this
    // class knows what she/he is doing
    explicit ErrorCode(ThrowableErrorCode<CODE>& code):
    code_(code.code_) {
        // prevent the throw
        code.throw_ = false;
    }
    operator CODE() {
        return this->code_;
    }
    ~ErrorCode()
    {}
};
Listing Three

So the caller to this kind of method has these alternatives:

Example 3 shows how you can use this new framework. Now when you revisit the problem encountered in Example 2, the code works fine because ErrorCodes ec1 and ec2 disarm the ThrowableErrorCodes that return from the fallable methods. That is, in case of an exception from these methods, the code reaches the catch statement just as you intended.

// function declaration
ThrowableErrorCode<int> FallableFunction(); 	


// ok
ErrorCode<int> result = FallableFunction();
if (0 == result) {...}	

// will throw - but will not terminate the process
if (FallableFunction ()) {...} 

// will throw - but will not terminate the process
FallableFunction(); 

// ok - Explicitly ignoring the error code
(IgnoreError) FallableFunction(); 

// might throw - the caller is taking a risk!
ThrowableErrorCode<int> mightThrow = FallableFunction(); 

Example 3: Example use of the new framework.

Acknowledgment

Thanks to Andrei Alexandrescu.

DDJ

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.