Forum Chat


Mar23,13:31 Johan Marechal
Wees gegroet
Sep20,17:50 Vicente Duque
Kim, Martin, Others :...
Jul07,11:10 Johan Marechal
PGP 9
Jul05,21:13 martin
Fastest in the bush
Jul05,07:48 martin
Spamdexing
Jun28,21:16 martin
New domain / new blog!
Jun28,21:11 martin
On posting etiquette

Exception handlers (C++)

Comment on this article

When writing exception handlers and error routines for C++ apps, in particular for COM objects, you need to decide on a strategy to avoid total confusion. The relevant points to remember (IMHO) are:

  • COM methods should never let an exception escape back to the caller (i.e. the COM runtimes).
  • Internal functions (by that I mean non-COM methods - non-interface methods) should largely use C++ exceptions to report errors.
  • Each function that catches exceptions and throws a new one should take care not to lose or overwrite relevant error information.
  • Each exception handler should "narrow" the range of exception types by catching more types of exceptions into a lower number of types. Sounds weird, but it ultimately means that the top level COM interface methods won't need to catch a whole range of different type of exceptions from below.
  • The COM methods report errors to the client using the Error() function belonging to the ISupportErrorInfo system. No other functions should use this mechanism, else the error info will be overwritten before returning to the client.

So, we want functions in our program to be able to catch exceptions and rethrow while adding some info of our own. Like:

void CSome::GetIt() throw(internal_error) 
{
  try 
  {
    BadFunction();
  }
  catch(const bf_exc& e)
  {
    throw internal_error(e, L"GetIt() failed");
  }
  catch(...)
  {
    throw internal_error(L"GetIt() - unknown exception")
  }
}

In this example we see that a multitude of possible exceptions (bf_exc plus all the stuff that can be described by the ellipsis in catch(...)) all get turned into one exception type, internal_error. Good thing. Makes the calling function so much simpler.

The top level, the COM method, must turn any exceptions into "normal" error messages. First of all, all interfaces should implement ISupportErrorInfo. Then they should look like this:

STDMETHODIMP CComObj::AMethod(LONG* plLongish)
{
  AFX_MANAGE_STATE(AfxGetStaticModuleState())
  try
  {
    m_pSome->GetIt();
    return S_OK;
  }
  catch(const internal_error& e)
  {
    return Error(e.msg());
  }
  catch(...)
  {
    return Error(L"Unhandled exception in CComObj::AMethod")
  }
}

The catch(...) should theoretically never be hit.

So how do we write the exception classes elegantly?

Since all exception classes need to take a number of different kinds of arguments, like strings, other exceptions of our own types, com exceptions, maybe yet more, it's a good idea to write a separate class that takes care of such conversions through a multitude of constructors. Then use that class as constructor argument for all our exception classes. That eliminates the need for quadratically expanding numbers of constructors. The converting class looks like:

class wrap_exc : public exc_base
  {
  public:
    wrap_exc(LPCWSTR pwszMsg) : exc_base(pwszMsg){};
    wrap_exc(const wstring& ws) : exc_base(ws){};
    wrap_exc(const _bstr_t& bstr) : exc_base((wchar_t*)bstr){};
    wrap_exc(const exc_base& qeb) : exc_base(qeb.msg()){};
    wrap_exc(const _com_error& e) : exc_base(e.Description()){};
  };

It simply extracts a wide string from whatever type of exception or object was passed to it and passes that on to its own base class. The base class in turn:

class exc_base
  {
  protected:
    wstring m_wsMessage;
  public:
    explicit exc_base(LPCWSTR pwszMessage) : m_wsMessage(pwszMessage) {};
    explicit exc_base(const wstring& ws) : m_wsMessage(ws) {};
    exc_base() {};
    virtual ~exc_base() {};
    LPCWSTR msg() const {return m_wsMessage.c_str();}
  };

This base class simply has the storage for the string, the constructors to load it and an accessor to get it out again.

Since all the exception classes have some commonality of work to do, like appending new info to the old info, we derive yet another base from this base:

class exception : public exc_base
  {
  public:
    exception() {};
    explicit exception(const wrap_exc& next) :
      exc_base(next.msg()) {} ;
    explicit exception(const wrap_exc& prev, const wrap_exc& next) {
      m_wsMessage = prev.msg();
      m_wsMessage += L"\n";
      m_wsMessage += next.msg();
    }
  };

And, then finally, we derive the "real" exception classes from this one, like:

class internal_error : public exception
  {
  public:
    internal_error() : exception(L"internal error") {};
    internal_error(const wrap_exc& next) : exception(next) {};
    internal_error(const wrap_exc& prev, const wrap_exc& next) :
      exception(prev, next) {};
  };

This one takes one or two arguments like in the code example on top. If it's thrown without any arguments, a default string ("internal error") is used. Remove that constructor if you want to force the user (coder) to provide a string.

If we look at the inheritance graph, it looks a bit funny:


The reason for the extra derivation from exc_base is that there's a mutual dependency going on here. If we try to create these classes without exc_base, then wrap_exc uses exception in its definition while exception uses wrap_exc. And that, as we all know, cannot be done in C++.

Oh, another little detail: I usually put my own exception classes in namespace x, making it easy to spot my own exception stuff in the code later. And allowing me to use class names like "exception" without fear.

Comment on this article

TOP