Working on a way to find all the derived classes of a base class: part 1 is here, discussing the motivation and frame of the problem. Â Part 2 is here, discussing one approach that works but not for me.
The problem is laziness: if I am in a hurry to put in a new error message and can build a version and send it to testing without updating the error class registry, then eventually I will do it. Â So how can we use laziness to our advantage? Â How can we make having a consistent error registry the easiest way to get the code to compile?
I found an article on codeproject that also provides a framework for runtime derived-class discovery. Â It’s substantially uglier than the meatspace solution quoted in part 2, but it addresses all of the weaknesses: index reversal, multiple hierarchies, and most importantly (for me) compile-time error if a derived class is not registered. Â What we’re going to wind up with is this:
class CError: {
DECLARE_ROOT_CLASS(CError);
public:
// body as in part 1
};
// error logging function
void errorlog( const CError& );
class CDerivedError: {
DECLARE_LEAF_CLASS(CError);
public:
CUnableToOpenFile() { Init( L"sample.txt", 2 ); }
CUnableToOpenFile( const wchar_t * psPath,
DWORD dwError = GetLastError() )
{ Init( psPath, dwError ); }
format_type Format() const
{ return "Unable to open file {0}: {1}"; }
};
Later on, in the implementation file for the error system:
IMPLEMENT_ROOT_CLASS(CError)
IMPLEMENT_LEAF_CLASS(CError,CUnableToOpenFile)
IMPLEMENT_LEAF_CLASS(CError,CBadMagicNumber)
// etc.
Why does this help? Why does it make any difference at all? By designing the system this way, there is now a chain of compile-time or link-time — at any case, not runtime — dependencies that make maintaining a consistent error table the easiest way, the laziest way, to add an error.
So under the hood, what are the macros doing? DECLARE_ROOT_CLASS() is the heaviest. It declares static member functions on CError, and it declares a vector of class factories as a static member variable. (Notice that it does not use the elegant Singleton pattern that the meatspace solution did, and I might change that.) It also declares a pure virtual function on CError — GetClassID().
IMPLEMENT_ROOT_CLASS() is mechanical, it just provides the implementation of the factory-adding mechanism.
DECLARE_LEAF_CLASS() is an interesting beast. It declares a static class variable. It implements two functions that are necessary to make CDerived a concrete class: it provides an implementation for GetClassID(), and it provides an implementation for CreateObject(), required by the class factory template. If I remove DECLARE_LEAF_CLASS, I get these errors:
errorlog.cpp(50) : error C2039: ‘CreateObject’ : is not a member of ‘CUnableToOpenFile’
e\ unabletoopenfile.h(3) : see declaration of ‘CUnableToOpenFile’
errorlog.cpp(50) : error C2259: ‘CUnableToOpenFile’ : cannot instantiate abstract class due to following members:
e\unabletoopenfile.h(3) : see declaration of ‘CUnableToOpenFile’
errorlog.cpp(50) : warning C4259: ‘int __thiscall CError::ClassID(void) const’ : pure virtual function was not defined errorlog.h(31) : see declaration of ‘ClassID’
And  IMPLEMENT_LEAF_CLASS() defines the static class variable that was declared by DECLARE_LEAF_CLASS().  The initialization of that static variable (at runtime, before main() executes) causes the class to be registered in CError’s factory table.  So if I create and use an error class but forget to use the IMPLEMENT macro, I get the following errors:
File.obj : error LNK2001: unresolved external symbol “private: static class CBootStrapper<class CError> CUnableToOpenFile::s_oBootStrapperInfo” (?s_oBootStrapperInfo@CUnableToOpenFile@@0V?$CBootStrapper@VCError@@@@A)
.debug/program.exe : fatal error LNK1120: 1 unresolved externals
- Convert an old-style error message to a new one by creating  a call to
errorlog( CErrorName( arg1, arg2 ) );
- Errorlog requires a class derived from CError, so create CErrorName as a new CError-derived class in a header file
- In order to derive from CError, we must supply bodies for pure virtual functions declared in CError; the simplest way to do that is with DECLARE_LEAF_CLASS
- External dependencies are created by DECLARE_LEAF_CLASS, and the simplest way to resolve them is to use IMPLEMENT_LEAF_CLASS
- IMPLEMENT_LEAF_CLASS causes the class to be registered in the error table.
One Comment