|
Boost : |
From: williamkempf_at_[hidden]
Date: 2001-04-17 08:57:35
--- In boost_at_y..., Jesse Jones <jesjones_at_h...> wrote:
> At 2:01 PM +0000 4/16/01, williamkempf_at_h... 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.
A "mixin" (a base class, actually) would need to use a virtual method
for the invariants check. Classes intended to not be used in a class
heirarchy should not be forced to use a mixin just to be able to
check invariants. It is possible to satisfy both types of class
design, simply by having the check_invariants class constructed by an
object pointer and a member function pointer and having "mixin"
classes supply a virtual member function here.
> 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.
Almost all C++ systems will make use of base classes or mixins to
some extent. However, no system I've ever seen uses these types
exclusively, and we should not force this on them just for assertion
handling.
> > > 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...
If an exception is thrown the postcondition is almost gauranteed to
fail. As for single entry/single exit functions... they're needed
for checking postconditions, not for checking invariants. However,
it would be nice if some mechanism could be devised that would allow
postconditions to work regardless of the number of exit points.
Whether a simple mechanism can be devised is questionable, but we
should try.
> >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.
Which is wasted memory and computation time when postconditions are
turned off. However, old() is easy to handle with a couple more
macros.
> >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.
No, this is not a mixin. The class heirarchy is not effected by the
use of invariant checking in any way. Notice that class foo has no
virtual methods and doesn't inherit from any classes, for instance.
The template allowed us to eliminate the mixin. Use of a class
function pointer could further reduce the requirements on the client
by not forcing the method name "invariants" on the class design. As
for the additional burden... this is where I said further templates
or macros could be of use. There's not much that a client must do
here, but even the extra bit of work can be delegated to a macro or
template or some other mechanism. I didn't propose this as the final
solution, only as an example of a simple way to eliminate the need
for a mixin, which is going to be a requirement, I think.
Bill Kempf
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk