The following is a set of coding style rules which all GUF code follows.
Some of the rules are more important than others, but the most important rule
is to be consistent. Even if a rule seems totally irrelevant (like where you
place braces surrounding a code block), consistently following the rule will
make your code easier to read and understand for other programmers.
- Global functions and variables are not used. All functions and variables must
be contained in classes. The only exception is overloaded operators.
- Use of macros is avoided when possible because macros do not obey scoping
- Everything is contained in some form of namespace. The top-level namespace
indicates the name of the software product. For example, all code in GUF
resides in the top-level namespace "guf". Similarily, all code in the C++
standard library resides in the namespace "std".
- Sub-namespaces are used to distinguish individual packages in the software.
Packages can contain sub-packages to any level.
- Each module consists of one main class. Any other classes that are part of
the module must be contained within the main class.
- Each module contains two source files:
- A header file defining the interface to the class. This file is nameded
after the class itself, which the file extension ".h". The file
contains an include guard to prevent problems if the header is included
twice. The include guard is a macro containing the complete namespace
and class name of the module, separated by underscores, and suffixed
with "__INCLUDED__". For example, guf::xml::GXmlParser's header is
named "GXmlParser.h" and looks something like this:
//insert interface here
- A source file defining the class implementation (if any). This should
be named after the class itself, with the file extension ".cpp".
- Each package consists of any number of modules and sub-packages.
- Packages are contained in directories named after the package.
- Package names follow the same rules as namespace names, since namespaces
are used to indicate packages.
- Compiled libraries
- Any module or package can be compiled into a library. The library is named
after the module or package with the complete scope indicated, separated by
underscores. The library is suffixed with a file type extension dependent
on the operating system. Some systems also specify a prefix. For example,
if you were to compile GXmlParser into a library on Linux, the name of the
library would be "libguf_xml_GXmlParser.so". On Windows, it would be
"guf_xml_GXmlParser.dll". However, normally you would not make a library
from a single class. In this case, you would probably compile GUF and all
sub-packages as one large library called "libguf.so" or "guf.dll".
- Each class is either pass-by-value, pass-by-smart-pointer, or
- Pass-by-value classes usually represent custom data types.
- All member variables are defined in the class's header.
- The class contains a copy constructor and a copy assignment operator.
These operators should be short enough to be inlined. Under no
circumstances should these operators do anything excessively
time-consuming, like allocating memory.
- The class may not inherit from any other class and may not contain any
virtual functions. No other class may inherit from a pass-by-value class.
- When trying to decide whether to pass an object by value or by reference,
ask yourself, "If this were an integer, what would I do?" If you would
pass the integer by value, pass the object by value. Generally, this
means that you should never find yourself passing const references around,
because you should be passing by value instead.
- Pass-by-smart-pointer classes represent larger objects.
- The classes never have a legal copy constructor or assignment operator.
- These classes are always derived from guf::GObject to allow smart pointers
to point at them.
- The class name with "Ptr" appended to the end should be typedef'ed as a
smart pointer to the class. For example, if you include the headers which
define guf::xml::GXmlParser, then guf::xml::GXmlParserPtr will also be
defined as a smart pointer to GXmlParser.
- Only smart pointers may point at these classes. Regular pointers are
never used with these as they could cause memory leaks and dangling
- The classes never have a legal constructor of any kind. Since these
classes must only have smart pointers pointing to them, you must call a
static function of the class to allocate one.
- Usually, the headers for pass-by-smart-pointer classes define only an
abstract interface (pure virtual) for the class. This interface then
contains a single static function called "New" which allocates and returns
a new instance of the class. The actuall class is defined in the
acompanying implementation file. This insures that the implementation can
be changed at will without breaking binary compatibility with previous
- Many of these classes are no more that abstract interfaces, and don't have
a New function.
- Pass-by-exception classes are thrown as exceptions.
- Exception classes are almost always defined within the scope of another
class. It usually doesn't make sense to have an exception as a separate
module, after all.
- All user-defined exceptions are derived from guf::GException.
- Exception classes must be pass-by-value safe. C++ requires this.
- Almost all functions have an exception specification. The only ones that
don't are inline functions and destructors, both of which are assumed to never
throw exceptions. Remember, calling a function with an exception
specification is, in fact, slower than calling one without.
- The C++ standard exceptions may be used, but use them sparingly. Normally,
bad_alloc is the only one of these that sees much use.
- There should be one exception class for each type of error that might occur.
For example, a function which opens a file should be able to throw both
"FileNotFoundException" and "AccessDeniedException" rather than having just
one "FileIOException". Better yet, both of the specific exceptions should be
derived from the general "FileIOException". This way, the caller can catch
the different exceptions individually and treat them differently, or can
catch all errors at once if preferred.
Some types of exceptions may also contain data. For example, an exception
thrown by an XML parser when a syntax error is found might contain the line
number and column number of the document where the error occurred.
- Be careful when deciding what should throw an exception. Here are a few
- Exceptions should only be thrown in conditions that would not normally
happen. A program should be able to run normally without any exceptions
- If a program has to call a function specifically to see if it throws an
exception, something is wrong. In these cases, a function should be
provided specifically to check for the exceptional condition without
actually raising any exceptions. For example, a function which opens a file
should throw an exception if the file is not found. However, some programs
may want to explicitly check to see if a file exists. In this case, the
program should be able to call a function specifically written for this
purpose. The function would simply check for the existance of the file and
return true or false.
Do not take this rule too far, however. Not all exceptional conditions
need to be explicitly checkable. For example, an XML parser might throw an
exception when it finds a syntax error in a document. However, providing a
specific function which checks for syntax errors would probably not be
useful. Use your judgement.
- Remember the key advantage of exceptions. Exceptions are better than
returning error codes because they force the user to check the error. Bugs
caused by unchecked return values in languages like C can be painful to
track down, whereas an uncaught exception can easily be tracked down by
catching it higher up in the stack and querying it.
However, the fact that exceptions must be caught can also be a disadvantage.
If the exceptional condition is one which the caller could safely ignore,
then think carefully about whether to throw. If you throw the exception,
the caller will not be able to ignore it. If everything throws exceptions
all the time, then things will become frustrating very quickly.
- Remember, like any C++ language feature, exceptions can be very useful, but
they can also be very frustrating if used wrong.