Boost logo

Boost :

From: Pavol Droba (droba_at_[hidden])
Date: 2004-10-08 15:24:43


On Fri, Oct 08, 2004 at 09:25:21PM +0200, Thorsten Ottosen wrote:
> > I'm sorry, but I must disagree with you analysis. You are looking to the
> > problem
> > only from the smart containers perspective. This leads to fundamentaly wrong
> > assumptions
> > and wrong results.
> >
> > Lets us think for a while what is the difference between null_object and 0
> > pointer.
> >
> > If we want to apply your semantics, null object must have fully defined
> > interface.
> > Although its function will probably do nothing, still they must behave
> > correctly.
> > So it is no longer a null_object. It is an object with a special state.
> >
> I don't understand why you can't give it special state; IMO it is not
> necessary, though.
>

You have only two options. Either you say, that the object is "null" and no
operations are valid on it (this is same as 0 pointer) or you will make
all its operation valid in the respect to the rest of the application.

Consider the object to have 10 interface functions. Each of them would
need to be able to work in special "null" mode or return "null" values
if they are called. This means, that instead one 0 problem you have 10 of them.

I can make you an example with any complexity you like.
 
> > If you relax this requiremnt you will get to the exactly same scenario as
> > with 0
> > poiner, only with slightly bigger overhead. Why? Becase if I designate
> > something
> > as null, I will have to check for it anyway. So your example will look like
> > this:
> >
> > for_each(.ptr_container_with_null_objects )
> > {
> > if(!i->is_null)
> > {
> > i->foo(); // non-virtual
> > i->bar(); // virtual
> > }
> > }
> >
> do you mean a dynamic cast? or how do ->is_null look like?

It does not matter how it will look like. If null object is special, it has to be
detectable as special so the application can act according to it.

>
> > Otherwise, you will have pay the comparison cost every time you perform an
> > operation
> > on the null object, and that might be on far more places.
> >
> not understood.

Try to ask Jeff Garland, how many complications it brought when a date class in date_time
library was enriched with the special values. Every operation must be aware of it.

>
> > In other words. If I designate an object as null, I want to threat it as an
> > invalid
> > null object. Therefor I will have to check every operation I would with 0
> > pointer.
> >

> that's not what null objects are for.

So what is the difference between your null-object and 0 pointer?
I see none, except in your case I will have to define special class/state
for every type I would like to use the smart_container with.

>
> > Also, if the design of my application needs to put only non-null objects
> > into the container,
> > I will NOT HAVE to check anything.
> >

> true, but you have lost the guarantee. Thus any interface with
> ptr_container foo();
> bar( const ptr_container& r );
>
> you have to document explicitly whether you allow null or not.

And the problem is where?

>
> > At most I will put asserts there, but definitely not
> > exceptions.
>
> by "there" you mean where?
>

For those operations that perform dereferencing, it should be part of smart_container code.
For others, it is up to the user to put asserts where appropriate.

> > So your arguments are quite misleading. If you allow 0 pointers, it is still
> > possible
> > to uses null_object pattern without any additional cost. The oposite is
> > obviosly not
> > feasible.
> >

> I need some real code to work with to be able to see this. Then I can rewrite
> it to compare.
>

// Some classes to work with
class Base
{
   virtual int Op1() =0;
...
   virutal int Op100() =0;
};

class A : public Base
{
   virtual int Op1() { // do something reasonable and return a valid value // }
   ..
   virtual int Op100() { // do something reasonable and return a valid value // }
};

class Null : public Base
{
   virtual int Op1() { return special_value1; }
   ..
   virtual int Op100() { return special_value100; }
};

// Now some operations, that use the object of Base.
// Assume we have 0 pointers

int foo1(Base* pObj)
{
   // Simple case -> check if oObj is not null
   if(pObj==0)
      return special_value;
   else
      return pObj->Op1() + pObj()->Op2() + ...... + pObj()->Op100();
}

// Now the same function, but we have null object

int foo2(Base& Obj)
{
   int result=0;

   if(Obj.Op1()!=special_value1)
   {
      result+=Obj.Op1()
   }
   else
   {
      return special_value;
   }
...

   if(Obj.Op100()!=special_value1)
   {
      result+=Obj.Op100()
   }
   else
   {
      return special_value;
   }

   return result;
}

This example might look realy stupid, but it illustrates well the problem I'm refering to.
You cannot assume, that null object will work in the same way as an ordinary one. Otherwise
you only pospone the checking one level further -> to its operations and the objects, that
uses it.

If you don't want to do this, you will have to check its null state just after you take
it from the container. Which is exactly the same operation as checking for 0.

Regards,

Pavol


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