Boost logo

Boost :

From: Jesse Jones (jesjones_at_[hidden])
Date: 2001-04-16 18:17:56


At 2:01 PM +0000 4/16/01, williamkempf_at_[hidden] wrote:
>An assertion mechanism should not intrude into a class design to the
>point of requiring a mixin.

That would be nice. However there's a real problem with systems that
make heavy use of mixins. If most of an object's public methods are
from mixins how can the assertion mechanism know what invariant to
call? I suppose the class that the mixins are mixed into could
override these functions, but that's an ugly burden to place on
clients.

I'm not sure how many real world systems make heavy use of mixins,
but the two most popular app frameworks on the Mac, PowerPlant and
MacApp, use them quite a bit.

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

Yep

>
> > I've sketched out a less intrusive solution below. [...] 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.

Right, if an exception is thrown the postcondition may fail. Consider
file::open(). The postcondition would be that the file was opened,
but if an exception was thrown it probably will not be open. OTOH if
you use an early return it would be nice to check the postcondition.
I don't see a way to reconcile these. My advice would be to use
single entry/single exit functions if you're checking invariants...

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

old() is nice, but it's usually not needed. If it is needed the
client can create an explicit temporary.

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

This is pretty much the mixin approach. The only difference is that
the boiler-plate code has been moved from the mixin to the
subclasses. I'm not too comfortable with the additional burden on
clients. I think it's important to make adding these checks as
painless as possible: each little hurdle we place in the way provides
an excuse to blow off the checks.

   -- Jesse


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