dbg library More...
Classes | |
struct | source_pos |
Data structure describing a position in the source file. More... | |
struct | dbg_exception |
The base type of exception thrown by dbg assertions (and other dbg library constraint checks) if the assertion_behaviour is set to assertions_throw. More... | |
struct | assertion_exception |
The type of exception thrown by assertion. More... | |
struct | sentinel_exception |
The type of exception thrown by sentinel. More... | |
struct | unimplemented_exception |
The type of exception thrown by unimplemented. More... | |
struct | check_ptr_exception |
The type of exception thrown by check_ptr. More... | |
class | null_stream |
In non-debug versions, this class is used to replace an ostream so that code will compile away. More... | |
struct | prefix |
struct | indent |
class | trace |
class | post_mem_fun |
class | post |
class | compile_assertion |
Typedefs | |
typedef const char * | dbg_source |
typedef for a string that describes the "source" of a diagnostic. | |
typedef const unsigned int | line_no_t |
Typedef used in the source_pos data structure. | |
typedef const char * | func_name_t |
Typedef used in the source_pos data structure. | |
typedef const char * | file_name_t |
Typedef used in the source_pos data structure. | |
typedef std::clock_t | dbgclock_t |
The dbgclock_t typedef is an unfortunate workaround for comptability purposes. | |
Enumerations | |
enum | level { info, warning, error, fatal, tracing, debug, none, all } |
The various predefined debugging levels. More... | |
enum | assertion_behaviour { assertions_abort, assertions_throw, assertions_continue } |
This enum type describes what happens when a debugging assertion fails. More... | |
Functions | |
void | enable (level, bool) |
void | enable (level, dbg_source, bool) |
void | enable_all (level, bool) |
null_stream | out (level, dbg_source) |
null_stream | out (level) |
void | attach_ostream (level, std::ostream &) |
void | attach_ostream (level, dbg_source, std::ostream &) |
void | detach_ostream (level, std::ostream &) |
void | detach_ostream (level, dbg_source, std::ostream &) |
void | detach_all_ostreams (level) |
void | detach_all_ostreams (level, dbg_source) |
null_stream | info_out () |
null_stream | warning_out () |
null_stream | error_out () |
null_stream | fatal_out () |
null_stream | trace_out () |
void | set_prefix (const char *) |
void | enable_level_prefix (bool) |
void | enable_time_prefix (bool) |
void | set_assertion_behaviour (level, assertion_behaviour) |
void | set_assertion_period (dbgclock_t) |
void | assertion (level, dbg_source, void *) |
void | assertion (level, void *) |
void | assertion (dbg_source, void *) |
void | assertion (void *) |
void | sentinel (level, dbg_source, void *) |
void | sentinel (level, void *) |
void | sentinel (dbg_source, void *) |
void | sentinel (void *) |
void | unimplemented (level, dbg_source, void *) |
void | unimplemented (level, void *) |
void | unimplemented (dbg_source, void *) |
void | unimplemented (void *) |
void | check_ptr (level, dbg_source, const void *, void *) |
void | check_ptr (level, const void *, void *) |
void | check_ptr (dbg_source, const void *, void *) |
void | check_ptr (const void *, void *) |
void | check_bounds (level, void *, int, int, void *) |
void | check_bounds (level, dbg_source, int, void *, void *) |
void | check_bounds (level, dbg_source, int, int, void *, void *) |
void | check_bounds (level, int, void *, void *) |
void | check_bounds (void *, int, void *, void *) |
void | check_bounds (int, void *, void *) |
Variables | |
const int | version = 110 |
This is the version number of the dbg library. | |
const dbg_source | default_source = 0 |
dbg library
The dbg library is a set of C++ utilities to facilitate modern debugging idioms.
It has been designed to support defensive programming techniques in modern C++ code. It integrates well with standard library usage and has been carefully designed to be easy to write, easy to read and very easy to use.
It provides various constraint checking utilities together with an integrated error logging facility. These utilities are flexible and customisable. They can be enabled and disabled at runtime, and in release builds, dbg library use can be compiled away to nothing.
Rich debugging can only be implemented in large code bases from the outset, it is hard to retrofit full defensive programming techniques onto existant code. For this reason it is good practice to use a library like dbg when you start a new project. By using dbg extensively you will find bugs quicker, and prevent more insideous problems rearing their head later in the project's life.
For instructions on the dbg library's use see the dbg namespace documentation. The dbg namespace holds a number of C++ debugging utilities.
They allow you to include constraint checking in your code, and provide an integrated advanced stream-based logging facility.
The characteristics of this library are:
Enabling debugging
To use dbg in your program you must include <dbg.h>
and compile with the DBG_ENABLED
flag set.
If you build without DBG_ENABLED you will have no debugging support (neither constraints nor logging). There is no overhead building a program using these utilities when DBG_ENABLED is not set. Well, actually there might be minimal overhead: there is no overhead when using gcc with a little optimisation (-O3
). There is a few bytes overhead with optimisation disabled. (The -O1
level leaves almost no overhead.)
Either way, the rich debugging support is probably worth a few bytes.
Once your program is running, you will want to enable diagnostic levels with dbg::enable, and probably attach an ostream (perhaps cerr
) to the diagnostic outputs. See the default states section below for information on the initial state of dbg.
Aside: The standard assert
macro is an insideous little devil, a lower case macro. This library replaces it and builds much richer constraints in its place. However, because of it, we have to use an API name dbg::assertion, not dbg::assert - this makes me really cross, but I can't assume that the user does not include <assert.h>
when using <dbg.h>
.
Using constraints
The dbg library constraints are very easy to use. Each debugging utility is documented fully to help you understand how they work. Here are some simple examples of library use for run-time constraint checking:
void test_dbg() { dbg::trace trace(DBG_HERE);
int i = 5; int *ptr =
dbg::assertion(DBG_ASSERTION(i != 6)); dbg::check_ptr(ptr, DBG_HERE);
if (i == 5) { return; }
// Shouldn't get here dbg::sentinel(DBG_HERE); }
The constraints provided by dbg are:
assert
) You can modify constriant behaviour with:
See their individual documentation for further details on usage.
You can specify whether constraints merely report a warning, cause an exception to be thrown, or immediately abort the program (see dbg::assertion_behaviour).
For assertions that may fire many times in a tight loop, there is the facility to time-restrict output (see dbg::set_assertion_period)
Using logging
All the constraint checking shown above integrates with the dbg library stream logging mechanisms. These logging facilities are open for your use as well.
Here is a simlpe example of this:
dbg::attach_ostream(dbg::info, cout); // now all 'info' messages go to cout
dbg::out(dbg::info) << "This is some info I want to print out\n";
dbg::out(dbg::tracing) << dbg::indent() << "This is output at 'tracing' level, indented " << "to the same level as the current tracing " << "indent.\n";
When you build without the DBG_ENABLED flag specified, these logging messages will compile out to nothing.
The logging is a very flexible system. You can attach multiple ostreams to any dbg output, so you can easily log to a file and log to the console, for example. The output can be formatted in a number of different ways to suit your needs.
The logging mechanisms provide you with the ability to prepend to all diagnostic output a standard prefix (see dbg::set_prefix), and also to add the diagnostic level and current time to the prefix (see dbg::enable_level_prefix and dbg::enable_time_prefix).
The logging facilities provide by dbg include:
The output formatting utilities include:
Diagnostic sources
The dbg library allows you to differentiate different "sources" of logging.
Each of the debug utilities has a second form in which you can supply a string describing the source of the diagnostic output (see dbg::dbg_source). This source may be a different software component, a separate file - whatever granularity you like!
If you don't specify a dbg::dbg_source then you are working with the ordinary "unnamed" source.
Using these forms you can filter out diagnostics from the different parts of your code. Each source can also be attached to a different set of streams (logging each component to a separate file, for example). The filtering is rich - you can selectively filter each different diagnostic dbg::level for each dbg::dbg_source. For example,
dbg::enable(dbg::all, "foo-driver", true); dbg::enable(dbg::all, "bar-driver", false);
int i = 5; dbg::assertion("foo-driver", DBG_ASSERTION(i != 6)); dbg::assertion("bar-driver", DBG_ASSERTION(i != 6));
This will trigger an assertion for the "foo-driver" but not the "bar-driver".
There is no requirement to "register" a dbg::dbg_source. The first time you use it in any of the dbg APIs, it will be registered with the dbg library. It comes into an existance as a copy of the "default" debugging sourcei, dbg::default_source.
The default source initially has all debug levels disabled. You can change that with this call. Note that this function only affects sources created after the call is made. Existing sources are unaffected.
If you don't know all of the dbg::dbg_source sources currently available, you can blanket enable/disable them with dbg::enable_all.
It can be tedious to specify the dbg_source in every dbg call in a source file. For this reason, you can specify the DBG_SOURCE compile time macro (wherever you specify DBG_ENABLED). When set, the calls automatically recieve the source name via the DBG_HERE macro (see dbg::source_pos for details). If DBG_SOURCE is supplied but you call a dbg API with a specific named dbg_source, this name will override the underlying DBG_SOURCE name.
Overloads
Each constraint utility has a number of overloaded forms. This is to make using them more convenient. The most rich overload allows you to specify a diagnostic dbg::level and a dbg::dbg_source. There are other versions that omit one of these parameters, assuming a relevant default.
Default states
When your program first starts up the dbg library has all debugging levels switched off. You can enable debugging with dbg::enable. All of the possible dbg::dbg_source enables are also all off for all levels. You can enable these with dbg::enable, or dbg::enable_all.
Initially, the std::cerr
stream is attached to the dbg::error and dbg::fatal diagnostic levels. You can attach ostreams to the other diagnostic levels with dbg::attach_ostream.
You can modify the "default state" of newly created debug sources. To do this use the special dbg::default_source source name in calls to dbg::enable, dbg::attach_ostream, and and detach_ostream. New sources take the setup from this template source.
All assertion levels are set to dbg::assertions_abort at first, like the standard library's assert macro. You can change this behaviour with dbg::set_assertion_behaviour. There are no timeout periods set - you can change this with dbg::set_assertion_period.
Debugging utilities
typedef const char* dbg::dbg_source |
typedef for a string that describes the "source" of a diagnostic.
If you are working on a large project with many small code modules you may only want to enable debugging from particular source modules. This typedef facilitiates this.
Depending on the desired granularity of your dbg sources you will use different naming conventions. For example, your dbg_sources might be filenames, that way you can switch off all debugging output from a particular file quite easily. It might be device driver names, component names, library names, or even function names. It's up to you.
If you provide the DBG_SOURCE macro definition at compile time, then the DBG_HERE macro includes this source name, differentiating the sources for you automatically.
typedef std::clock_t dbg::dbgclock_t |
The dbgclock_t typedef is an unfortunate workaround for comptability purposes.
One (unnamed) popular compiler platform supplies a <ctime> header file, but this header does NOT place the contents into the std namespace.
This typedef is the most elegant work around for that problem. It is conditionally set to the appropriate clock_t definition.
In an ideal world this would not exist.
This is the version for sane, standards-compliant platforms.
typedef const char* dbg::file_name_t |
typedef const char* dbg::func_name_t |
Typedef used in the source_pos data structure.
Describes a function name in a source file. (Can be zero to indicate the function name cannot be assertained on this compiler).
typedef const unsigned int dbg::line_no_t |
Typedef used in the source_pos data structure.
Describes a line number in a source file.
This enum type describes what happens when a debugging assertion fails.
The behaviour can be:
The dbg library defaults to assertions_abort behaviour, like the standard C assert
.
enum dbg::level |
The various predefined debugging levels.
The dbg API calls use these levels as parameters, and allow the user to sift the less interesting debugging levels out through dbg::enable.
These levels (and their intended uses) are:
const int dbg::version = 110 |