Boost logo

Boost :

From: Jesse Jones (jesjones_at_[hidden])
Date: 2001-04-14 08:31:48


At 4:22 PM +0000 4/13/01, williamkempf_at_[hidden] wrote:
>I've uploaded two files to an assert folder on the files section.
>Don't expect the code provided there to be of much use yet, other
>than being a starting point for discussion (including whether or not
>such a library should be included in Boost). The design follows DbC
>as described in OOSC2 by Bertrand Meyers and thus is similar to
>assertions in Eiffel. Do NOT expect full DbC support. I'm not sure
>that full DbC would be possible with C++ and know that it won't be
>possible with out language support and/or a smart pre-processor.
>What's provided is very minimalist, but will cover most needs for
>error handling and debugging.

I've been thinking about how to handle invariants. The tricky bit is
that you want to check invariants both when you enter and exit a
public method. Of course you can exit via a return statement or an
exception so a stack based class seems to be the way to go. It's also
really nice if invariants are only checked when control first passes
into the object: while a method executes the object's state may
temporarily become invalid. The way I've handled this in the past is
with a mixin that counts the nesting level of calls into the object.

I've sketched out a less intrusive solution below. First there are a
pair of functions that we call when we enter and exit a method with a
precondition check. entering_object() returns true if we're not
making a recursive call into the object. Then there's a template
function that makes the call to the invariant (this is a bit of
voodoo that allows us to call the invariant without knowing the
object type). Then there's the stack based class that ties everything
together. I didn't see a way to make this a template and didn't want
to call operator new so the call to the invariant in the dtor is made
through a pointer to a function. Finally there are a couple of macros
that clients use. Here's the code:

bool entering_object(const void* thisPtr);
void leaving_object(const void* thisPtr);

template <class T>
void invoke_invariant(const void* thisPtr)
{static_cast<const T*>(thisPtr)->invariant();}

class check_invariant {
public:
     ~check_invariant()
{leaving_object(mThisPtr); if (mInvoker) mInvoker(mThisPtr);}
    
     template <class T>
     check_invariant(const T* thisPtr) : mThisPtr(thisPtr)
{mInvoker = nil; if (entering_object(mThisPtr))
     mInvoker = invoke_invariant<T>;}
private:
     void (*mInvoker)(const void*);
     const void* mThisPtr;
};

#define PRECONDITION(p) ASSERT(p); \
                           (void) check_invariant(this)

#define POSTCONDITION(p) ASSERT(p)

There are a few annoyances:

1) entering_object() and leaving_object() will make use of something
like a std::map. When the nesting count goes to zero the
corresponding entry can be removed. This is pretty straight-forward
but the map will need to be wrapped with a mutex.

2) In the past I've made my invariant methods protected, but this
requires them to be public.

3) One nice feature of my old macros was that it dynamic-casted
'this' to my invariant mixin. This allowed invariants to be checked
inside other mixins. For example, I used to have mixins for things
like key and menu handling (I use a COM-like architecture now). For
many objects the bulk of the calls were routed though mixins.

   -- Jesse


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