Boost logo

Boost :

From: williamkempf_at_[hidden]
Date: 2001-04-16 09:01:44


--- In boost_at_y..., Jesse Jones <jesjones_at_h...> wrote:
> At 4:22 PM +0000 4/13/01, williamkempf_at_h... 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.

An assertion mechanism should not intrude into a class design to the
point of requiring a mixin. However, these ideas could be expanded
to form a more general mechanism that addresses several of these
details. What I posted doesn't address invariants any more than to
supply the macro used to specify an invariant. This is the minimal
amount of functionality needed to insure checking when and only when
specified at compile time. There are a whole host of other issues
involved with using invariants in an OO system, not all of which can
be handled in a language like C++, but I figured a partial solution
is bettern than no solution here. Once we've addressed some of the
concerns I posted we can then start addressing ways in which the
assertions can be used to more closely fullfill the requirements of
DbC despite the language shortcomings in this area.

> 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)

This is a good start. However, notice that POSTCONDITION has some of
the same problems as INVARIANT. It's hard to insure the checks are
made when leaving the function when you consider exceptions
(actually, you probably don't want to invoke it then) or multiple
return paths. There's also the need for an old() method to compare
values at the start and finish of a method. If you can solve these
issues then invariants become even easier.

> 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.

template <typename T>
class check_invariant
{
public:
   check_invariant(const T* pthis) : m_pthis(const_cast<T*>(pthis)) {
      m_pthis->invariant(1);
   }
   ~check_invariant() {
      m_pthis->invariant(-1);
   }
private:
   T* m_pthis;
};

class foo
{
public:
   int inv_count;
   void invariant(int c) {
      if (c > 0 && inv_count != 0) {
         inv_count++;
         return;
      }
      if (c < 0 && inv_count != 1) {
         inv_count--;
         return;
      }
      INVARIANT(true); // should be a real check
   }

   void bar1() {
      check_invariant<foo> check(this);
      bar2();
   }
   void bar2() const {
      check_invariant<foo> check(this);
   }
};

There's a little burden on the programmer with this construct, but
it's non-intrusive on the design and handles all the points you tried
to address. With some macro magic or further intelligent use of
templates this idea can be made even simpler for the developer.

Bill Kempf


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