Boost logo

Boost :

From: Dan W. (danw_at_[hidden])
Date: 2004-01-06 15:54:19


//file: invariants.hpp
#ifndef INVARIANTS_HPP
#define INVARIANTS_HPP

/*
   Terminology:

   By the term 'invariants', it is meant relationships that must
   hold true regarding the state of a class, at the time that a
   public function is entered or exited.

   By 'state' it is meant the combined values of its members.

   By 'members' it is meant the "raw bits" of the class footprint.

   Invariants-checks, as a debugging aid, may, but need not cover
   all possible invariants. Expensive checks are better avoided,
   as they will be invoked at every public function call.

   Checks are only strictly necessary either at entry or at exit
   points of functions, not both. See under 'Rationale'.

   Invariants checks are not to be applied to private, protected
   or const functions; nor, of course, to static functions; and
   they are only compiled in debug mode.

   Invariants must NOT themselves be thread-safe. More details on
   this below, under Thread Safety.

   Description:

   Abstract class 'invariants' is imbues a derived class with a
   basic mechanism for invariant checking, via inheritance.
   Pure virtual function 'void verify_invariants() const' must be
   defined in the derived class.

   Nested class invariants::trigobj is a class whose destructor
   triggers a call to invariants::verify_invariants(). It is to
   be instantiated as the first automatic variable in every non-
   -const public function of the derived class.

   Rationale: The call to verify invariants could be made at the
   entry and exit points of functions, but one of the two is
   sufficient, provided the class does not have evil friends.
   Checking only at exit was chosen as the policy in order to
   catch a bug within the function where the bug resides, rather
   than at the time of the next public function call.
   This choice, however, would make the placing of calls to check
   invariant more laborious and error-prone, as each function may
   have multiple return points. Ergo, the use of a trigger class
   to be instantiated first on the stack within each public, non-
   -const function.

   Invariants should also be checked after initialization and
   before destruction. Functions 'void first_verify() const' and
   'void last_verify() const' are provided for this purpose. The
   call to first_verify can be made from the bodies of ctors, but
   after any other state-modifying operations therein. The call
   to last_verify may be placed as the first statement within the
   bodies of destructors. A boolean member 'enabled_' is set to
   true by first_verify, and to false by last_verify. Calls to
   verify_invariants (triggered by the destructor of trigobj vars
   upon returning from non-const public functions) should occur
   while enabled_ is true, otherwise an internal assertion will
   stop execution. Other internal assertions include checks to
   ensure that enable_ isn't set to true (or false) repeatedly.

   All member functions are no-throw, unless bad-alloc is thrown
   during construction, which everybody knows, so I skipped the
   no-throw comments.

   Thread Safety:

   Class invariants is not designed to be thread-safe, and should
   not be. The rationale is that if the derived class itself is
   thread-safe, so will class invariants within it. Otherwise,
   there are two possibilities: either the derived class does not
   need thread safety (as it may be in use within a single thread
   environment, or only called from within one thread; or else
   the derived class does need, but does not have, thread safety,
   due to programmer error. In this last case, class invariants
   will probably find the state of the object incoherent, at some
   point, which will alert the programmer to the situation.
   This last benefit would be lost if class invariants were itself
   thread-safe.

   To be done:

   At this time, class invariants would present problems when
   deriving a concrete class from another concrete class that is
   already derived from class invariants. (Concrete derivation is
   a deprecated design choice, and therefore I don't see this as
   being a show-stopper. Ref: Scott Meyers.) The problem results
   from the fact that the intermediate derived class may have
   placed calls to first_verify and last_verify in its ctor/dtor,
   which may now miss added state by its derived class. Not sure
   how this problem could be solved.

   Macros may present some issues, of course...
*/

#ifdef JUST_ME
# include "ensure.hpp"
#else
# include <cassert>
# define ENSURE(x) assert(x)
#endif

namespace boost { namespace DBC {

struct invariants
{
     struct trigobj //instantiate in derived class public,
     //non-const functions. May instantiate in const ones
     //also, if paranoid. May NOT instantiate in private
     //or protected functions! Syntax: "trigobj t(this);".
     //Place such a line as first line in each function.
     {
         trigobj( invariants const * pinv )
         : pinv_( pinv )
         {
             ENSURE( pinv_ );
         }
         ~trigobj()
         {
             ENSURE( pinv_ );
             if( pinv_ )
                 pinv_->verf_invars();
             pinv_ = 0;
         }
      private:
         invariants const * pinv_;
         trigobj(); //no default ctor.
         trigobj( trigobj const & );
         void operator=( trigobj const & );
     };
     //invariants base is copying-immutable:
     invariants(invariants const &) : enabled_(false) {}
     invariants & operator=(invariants const &){ return *this; }
     void verf_invars() const
     {
         ENSURE( enabled_ ); //forgot to enable?
         verify_invariants();
     }
  protected:
     invariants() : enabled_(false) {}
     void first_verify() const
     {
         ENSURE( ! enabled_ ); //enabled by ancestor ctor?
         enabled_ = true;
         verify_invariants();
     }
     void last_verify() const
     {
         ENSURE( enabled_ ); //forgot to ever enable?
         verify_invariants();
         enabled_ = false;
     }
     ~invariants()
     {
         ENSURE( ! enabled_ ); //not disabled in derived dtor?
         enabled_ = false;
     }
     mutable bool enabled_;
  private:
     virtual void verify_invariants() const = 0;
};
                 } }
#ifdef _DEBUG
# define INVARIANT_DERIVED : public invariants
# define INVARIANT_CHECKED trigobj TRIGOBJ(this);
# define FIRST_INVARIANT_CHECK first_verify();
# define LAST_INVARIANT_CHECK last_verify();
#else //release mode:
# define INVARIANT_DERIVED
# define INVARIANT_CHECKED
# define FIRST_INVARIANT_CHECK
# define LAST_INVARIANT_CHECK
#endif

#endif
----------------------------------------------------------
//invariants_test.cpp
#include "invariants.hpp"

using namespace boost::DBC;

class my_class INVARIANT_DERIVED
{
     int a, b;
     void verify_invariants() const
     {
         ENSURE( a + b == 5 );
     }
  public:
     my_class() : a(2), b(3)
     {
         FIRST_INVARIANT_CHECK
     }
     ~my_class()
     {
         LAST_INVARIANT_CHECK
     }
     void f() //public, non-const: must check
     {
         INVARIANT_CHECKED
         ++a; --b;
     } //ok
     void g() //public, non-const: must check
     {
         INVARIANT_CHECKED
         ++a;
     } //boom!
};

int main()
{
     my_class my_obj;
     my_obj.f();
     my_obj.g();
     return 0;
}
----------------------------------------------------------

Works like a charm. Cheers!


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk