Exception Handling Design 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 &lt;TCHAR&gt; tstring ; 
#endif
struct BaseException // Derive from some standard exception
{
BaseException () ;
BaseException (const tstring &amp;strWhat) ;
BaseException (const tstring &amp;strWhat, const unsigned long ulErrorcode) ;

// ...Omitted functions here

void AddDetail (const tstring &amp;strWhat) ;
void AddDetail (const tstring &amp;strWhat, const unsigned long ulErrorcode) ;
tstring GetErrorString () const ;

private:
bool m_bValidErrorCode ;
std::vector &lt;tstring&gt; 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 &amp;strFile, const tstring &amp;strSection, 
    const tstring &amp;strKey, const bool bRequired = true, 
    const tstring &amp;strDefault = _T ("")) const ;

void WriteValue (const tstring &amp;strFile, const tstring &amp;strSection,
    const tstring &amp;strKey, const tstring &amp;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?
1) 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?


Here is my opinion: What is a 'good number' of exceptions to implement for my library?


  2) 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?


You should probably derive from std::runtime_error.


  3) 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?


Use the string in the exception solely for logging (not for generating a user error message). The exception will generally carry technical information that is not relevant to the user.

But when displaying information to the user you should generate a formatted string at the catch point (not the throw point). This is because you can not tell what the output device is going to be used at the throw point.