I am trying to design an exception hierarchy for logging errors.
These logs will be used only by developers and not by users.
This is a base class that I came up with.
#ifndef TSTRING_TYPEDEF
#define TSTRING_TYPEDEF
typedef std::basic_string <TCHAR> tstring ;
#endif
struct BaseException // Derive from some standard exception
{
BaseException () ;
BaseException (const tstring &strWhat) ;
BaseException (const tstring &strWhat, const unsigned long ulErrorcode) ;
// ...Omitted functions here
void AddDetail (const tstring &strWhat) ;
void AddDetail (const tstring &strWhat, const unsigned long ulErrorcode) ;
tstring GetErrorString () const ;
private:
bool m_bValidErrorCode ;
std::vector <tstring> m_vecDetails ;
unsigned long m_ulErrorCode ;
};
The idea is that an inner function will not always have all the necessary details needed to give useful debugging information, so an inner function will load up an exception with whatever details it can and then throw the exception. The exception will be caught where it can be handled and more information may be added.
The error code will usually just be whatever ::GetLastError()
returns (if applicable).
Please note that I won't be catching/wrapping bad alloc
calls with this, so I'm not worried about making heap allocations with string
and vector
.
My questions:
- I've been searching online for exception class design, and haven't found anything like this. This worries me. Is there something wrong with designing exceptions this way?
- Are there any considerations to think about when deriving from one of the standard exceptions? Or should I just derive from std::exception and not worry about?
- One problem with this design is that the format of the string is dependent on this class. Is there any good way to decouple this?
For example, let's a say we have files like this:
MainProcess.h
class MainProcess
{
public:
// ctors, dtor, functions, etc...
private:
Config m_config ;
// ...
};
Config.h
class Config
{
public:
// ctors, dtor, etc...
std::string ReadValue (const tstring &strFile, const tstring &strSection,
const tstring &strKey, const bool bRequired = true,
const tstring &strDefault = _T ("")) const ;
void WriteValue (const tstring &strFile, const tstring &strSection,
const tstring &strKey, const tstring &strValue) const ;
};
class ConfigReadException // Derive from something
{
// ctors, dtor, etc...
private:
tstring strFile ;
tstring strSection ;
tstring strKey ;
tstring strDefault ;
};
class ConfigWriteException // Derive from something
{
// ctors, dtor, etc...
private:
tstring strFile ;
tstring strSection ;
tstring strKey ;
tstring strValue ;
};
In this case, these ConfigExceptions
provide only relevant data and no specific format. But if we do this, then we may end up having to catch many different kinds of exceptions that would clutter our code.
Is it a better practice to have exception classes similar to BaseException
or is it better to have exception classes to similar to ConfigReadException
and ConfigWriteException
, or is there a middle ground?