...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
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:
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 unequal_allocators // allocators ought to be equal }; } // 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.
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);
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 May 9th 2020
© Copyright 2003-2020 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)