The Admin library
The Admin library is a collection of header files that all other libraries in the JetByte Tools suite use. Header files from the Admin library control things such as which warnings are disabled (Warnings.h) and which version of windows you're targetting the code for (see here for details of just how complex that simple thing could be...). See here for code that implements this concept.
Warnings level 4
All code builds at warning level 4. The Admin library contains a header file, Warnings.h that disables warnings that "don't matter", things like C4786 - "identifier was truncated to '255' characters in the debug information" and C4511 - "copy ctor could not be generated", etc. Some files may contain individual pragmas to disable specific warnings but we try and keep these to a minimum, things like C4355 - 'this' used as base member initialiser only. See here for code that implements this concept.
Warnings as errors
It's no good having your warning setting set to high if you're then allowed to ignore the messages. To that end we tell the compiler to treat warnings as errors. This means that you MUST act to remove warnings as they occur.
Unicode and Non-Unicode Builds
All of the code builds as either Unicode or Non-Unicode. This allows us to be maximally efficient on our primary platform (Windows NT/XP, etc) and yet still be able to use the code on older platforms (Windows 95) if required. See here for code that implements this concept.
Multi-Compiler support
All of the code builds with all versions of Visual Studio from Visual Studio 6 through to Visual Studio 2005. The structure of the project files is such that each compiler builds to its own output directory so that you can run multiple builds on multiple compilers at the same time.
Precompiled Headers
All of the code builds using precompiled headers for speed of compilation using #pragma hdrstop to detail where the precompiled header stops. There's also always a 'no precompiled header' build which is used to make sure that include file dependencies are 'correct' and kept to a minimum. In a nutshell, putting #pragma hdrstop in a source file that isn't compiled with /Yc or /Yu has no effect at all. If you have /Yu set for the file then hdrstop tells the compiler to throw away everything before the line on which hdrstop appears and insert the precompiled header instead. If /Yc is set for the file then hdrstop means save all of the compiled state for everything up to the line on which hdrstop appears as the precompiled header. The trick is using /Yc and /Yu without the optional header file name; just check the 'use' or 'create' radio button and leave the 'through header' edit box blank (or edit the dsp). See here for more details and here for code that implements this concept.
Debug logging and tracing
We use minimal debug tracing in the form of calls to JetByteTools::Win32::Output and (even less often) JetByteTools::Win32::OutputEx. If an application requires a trace log for support purposes then we implement one, probably using asynchronous file I/O to write the log using the JetByteTools::IO::CAsyncFileLog logger. A support log is NOT, in our opinion, the same as a debug trace, it's part of the application and should be designed with as much care as the rest of the application. Debug trace files tend to accumulate rubbish over time as trace statements are put in to help debug problems. See: here and here for more details.
Logging Log files and the pluggable logging system
Having explained our lack of enthusiasm for Debug tracing you might be surprised to find that there is a fairly complex pluggable logging system underneath our basic JetByteTools::Win32::Output calls. This can be customised to provide a high performance trace log for supporting software that's been written with the framework. You can either use one of the many implementations of JetByteTools::ILogMessages or write your own and plug it in. Alternatively you can take a look at JetByteTools::CDebugTrace or JetByteTools::Win32::CDebugTrace (don't ask!) and write something similar to provide a separate system trace and debug log system, or use the log writers for other purposes...
Interfaces
These abstract base classes define Interfaces that concrete classes implement. They exist so that we can reduce coupling in the system. Since objects interact with each other through abstract interfaces and they don't care about the implementation of those interfaces we're free to plug in any object that implements the interface, such as a Mock Object, or alternative implementation. This allows us to evolve the system in a gradual manner; isolated objects can be replaced by new objects that implement the same interfaces without causing changes to ripple through the entire system due to unexpected coupling. See here and here for more details and here for code that implements this concept.
Protected non-virtual destructors on abstract base classes
Many of the abstract base classes that we use as interfaces have protected non-virtual destructors rather than the more usual public virtual destructors. This is because we never delete an instance of the object through a pointer to the interface concerned and by making the destructor protected and non-virtual we indicate that fact (and also make such deletion impossible). See here for more details and here for code that implements this concept.
Exceptions
Most of this code uses exceptions for the reporting of errors. Most functions validate their input and throw exceptions on bad input. Most fuctions throw exceptions if there are errors that they can't handle. There are a surprisingly small number of exception classes used, this is by design. Usually there isn't a great deal that the calling code will want to do that depends on why the code failed, if there's that kind of choice available then the code that's being called usually exposes that choice. This means that if an exception occurs it's often not caught by anything except the final exception handler in the system, the one that lives at process or thread boundaries and, generally, pretty much all that will do is log the failure. This is all by design and all works well for writing reliable code. See here, here, here and here for more details and here for code that implements this concept.
Exception Specifications
Whilst exception specifications in C++ can, at first glance, seem like a good idea, they're not. The problem is that, unlike Java, the exception specification is not enforced at compile time, they're just a runtime thing. If an exception that isn't in the exception specification is thrown by a function then unexpected() may (or may not if you're Visual C++) be called. As such we view exception specifications as of limited use in C++. However: we do use a 'no throw' (throw()) exception specification to indicate that a function does not throw exceptions. We rarely use a 'no throw' specification to indicate that YOU should not throw exceptions, simply because we don't trust you ;) See here for code that implements this concept.
Templates
We don't use templates very often (apart from the STL, of course!). This is deliberate. We feel that often templates are overused and often the code using them can be more complex than need be. And, we're constrained by the fact that we need to support old compilers that often don't allow us to use templates when we'd like to... We do use templates where we can and where we feel that they add value to the construction and maintenance of the code. See here for code that implements this concept.
Monitoring
Many classes support a monitoring interface. This allows us to see what's going on and, perhaps, hook these classes up to performance counters that can be viewed by perfmon, etc. See here for code that implements this concept.
Patterns with a small 'p'
Patterns are a great way of being able to communicate clearly about a programming concept, but some people get a little too hung up on them. We use patterns sparingly and, hopefully, only where appropriate... The most common patterns in use in our code are listed below:
1.5.3