|
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