boost.png (6897 bytes)Boost.MultiIndex Tutorial: Debugging support



Contents

Debugging support

The concept of Design by Contract, originally developed as part of Bertrand Meyer's Eiffel language, revolves around the formulation of a contract between the user of a library and the implementor, by which the first is required to respect some preconditions on the values passed when invoking methods of the library, and the implementor guarantees in return that certain constraints on the results are met (postconditions), as well as the honoring of specified internal consistency rules, called invariants. Eiffel natively supports the three parts of the contract just described by means of constructs require, ensure and invariant, respectively.

C++ does not enjoy direct support for Design by Contract techniques: these are customarily implemented as assertion code, often turned off in release mode for performance reasons. Following this approach, Boost.MultiIndex provides two distinct debugging modes:

These two modes are independent of each other and can be set on or off individually. It is important to note that errors detected by safe mode are due in principle to faulty code in the user's program, while invariant-checking mode detects potential internal bugs in the implementation of Boost.MultiIndex.

Safe mode

The idea of adding precondition checking facilities to STL as a debugging aid was first introduced by Cay S. Horstmann in his Safe STL library and later adopted by STLport Debug Mode. Similarly, Boost.MultiIndex features the so-called safe mode in which all sorts of preconditions are checked when dealing with iterators and functions of the library.

Boost.MultiIndex safe mode is set by globally defining the macro BOOST_MULTI_INDEX_ENABLE_SAFE_MODE. Error conditions are checked via the macro BOOST_MULTI_INDEX_SAFE_MODE_ASSERT, which by default resolves to a call to BOOST_ASSERT.

If the user decides to define her own version of BOOST_MULTI_INDEX_SAFE_MODE_ASSERT, it has to take the form

BOOST_MULTI_INDEX_SAFE_MODE_ASSERT(expr,error_code)

where expr is the condition checked and error_code is one value of the safe_mode::error_code enumeration:

namespace boost{

namespace multi_index{

namespace safe_mode{

enum error_code
{
  invalid_iterator,             // vg. default cted or pointing to erased element
  not_dereferenceable_iterator, // iterator is not dereferenceable
  not_incrementable_iterator,   // iterator points to end of sequence
  not_decrementable_iterator,   // iterator points to beginning of sequence 
  not_owner,                    // iterator does not belong to the container
  not_same_owner,               // iterators belong to different containers
  invalid_range,                // last not reachable from first
  inside_range,                 // iterator lies within a range (and it mustn't)
  out_of_bounds,                // move attempted beyond container limits
  same_container                // containers ought to be different
};

} // namespace multi_index::safe_mode

} // namespace multi_index

} // namespace boost

For instance, the following replacement of BOOST_MULTI_INDEX_SAFE_MODE_ASSERT throws an exception instead of asserting:

#include <boost/multi_index_container/safe_mode_errors.hpp>

struct safe_mode_exception
{
  safe_mode_exception(boost::multi_index::safe_mode::error_code error_code):
    error_code(error_code)
  {}

  boost::multi_index::safe_mode::error_code error_code;
};

#define BOOST_MULTI_INDEX_SAFE_MODE_ASSERT(expr,error_code) \
if(!(expr)){throw safe_mode_exception(error_code);}

// This has to go before the inclusion of any header from Boost.MultiIndex,
// except possibly safe_error_codes.hpp.

Other possibilites, like outputting to a log or firing some kind of alert, are also implementable.

Warning: Safe mode adds a very important overhead to the program both in terms of space and time used, so in general it should not be set for NDEBUG builds. Also, this mode is intended solely as a debugging aid, and programs must not rely on it as part of their normal execution flow: in particular, no guarantee is made that all possible precondition errors are diagnosed, or that the checks remain stable across different versions of the library.

Serialization and safe mode

Iterators restored from an archive are not subject to safe mode checks. This is so because it is not possible to automatically know the associated multi_index_container of an iterator from the serialization information alone. However, if desired, a restored iterator can be converted to a checked value by using the following workaround:

employee_set es;
employee_set::nth_index<1>::iterator it;

// restore es and it from an archive ar
ar>>es;
ar>>it; // it won't benefit from safe mode checks

// Turn it into a checked value by providing Boost.MultiIndex
// with info about the associated container.
// This statement has virtually zero cost if safe mode is turned off.
it=es.project<1>(it);

Invariant-checking mode

The so called invariant-checking mode of Boost.MultiIndex can be set by globally defining the macro BOOST_MULTI_INDEX_ENABLE_INVARIANT_CHECKING. When this mode is in effect, all public functions of Boost.MultiIndex will perform post-execution tests aimed at ensuring that the basic internal invariants of the data structures managed are preserved.

If an invariant test fails, Boost.MultiIndex will indicate the failure by means of the unary macro BOOST_MULTI_INDEX_INVARIANT_ASSERT. Unless the user provides a definition for this macro, it defaults to BOOST_ASSERT. Any assertion of this kind should be regarded in principle as a bug in the library. Please report such problems, along with as much contextual information as possible, to the maintainer of the library.

It is recommended that users of Boost.MultiIndex always set the invariant-checking mode in debug builds.




Revised February 6th 2006

© Copyright 2003-2006 Joaquín M López Muñoz. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)