Boost logo

Boost :

From: Fernando Cacciola (fcacciola_at_[hidden])
Date: 2001-07-19 15:04:05


In order to introduce this proposal let's consider the following situations:

CASE A:
=======

struct image
{
  ...
  int get_pixel( int x, int y) const ;
  ...
} ;

How should we deal with the case of x,y being outside the image?
Unfortunately, it might be the case that every possible integer value is a
valid pixel color, so no value can be used to signal that there is no pixel
at that position.
Assume that the function should not throw an exception. The only choice is
to carry a boolean flag along.

Options are:

  std::pair<int,bool> get_pixel( int x, int y) const ;

  or

  int get_pixel( int x, int y, bool& ) const ;

both options have the drawback that the user must deal with a rather
complicated interface.

CASE B:
=======

Consider a typical algorithm that seeks to compute a value, for example in a
loop, but with the postcondition that the value might not be defined.
A typical implementation uses a separate boolean flag that must be
synchronized with the value assignment:

  bool initialized ;
  int value ;

  while ( ... )
  {
    if ( foo() )
    {
      value = 3 ;
      initialized = true ;
    }
  }

  if ( initialized )
    ....

This is not only more complicated that it should, but also error prone
because of the required synchronization.

CASE C:
=======

struct polygon
{
  polygon() : m_last_point_defined(false) {}

  point m_last_point ;
  bool m_last_point_defined ;

  void set_origin ( point const& p )
  {
    m_last_point = p ;
    m_last_point_defined = true ;
  }

  void add_segment_to ( point const& p )
  {
    if ( m_last_point_defined )
      segments.push_back ( segment(m_last_point,p) ) ;
    else
      throw std::logic_error ("must call set_origin() first!") ;

    m_last_point = p ;
    m_last_point_defined = true ;
  }

  ...
} ;

This is a typical situation of a state variable that is uninitialized before
a given event.
This also requires a separate boolean flag that must be properly initialized
and kept synchronized.

Now, all those problems arise because for many types there's no value that
means 'uninitialized'.
There is, however, on type of variable that has a widely used and
unambiguous 'uninitialized' value: pointers.
A null pointer could be used to signal all the above cases. Unfortunately,
pointers are valid only as long as the objects they point to are allocated,
and this could mean a heap allocation.

There is a nice and compact solution to this problems: 'statefull objects'.
A statefull object is an rvalue with a pointer-like-semantic and an attached
boolean initialized
flag.

I uploaded statefull.cpp to the files section, were you can see this in more
detail.

Here are the statefull equivalents of the above cases:

CASE A:
=======
struct image
{
  ...
  statefull_t<int> get_pixel( int x, int y) const ;
  ...
} ;

statefull_t<int> color = img.get_pixel(x,y);
if ( color != NULL )
  cout << *color << endl ;

CASE B:
=======

  statefull_t<int> value ; // starts in uninitialized state.

  while ( ... )
  {
    if ( foo() )
    {
      // changes to initialized state upon assignment.
      *value = 3 ;
    }
  }

  if ( !value )
    // not found, do something else to compute it.

CASE C:
=======

struct polygon
{
  polygon() {} // m_last_point defaults to uninitialized.

  statefull_t<point> m_last_point ;

  void set_origin ( point const& p )
  {
    *m_last_point = p ;
  }

  void add_segment_to ( point const& p )
  {
    if ( m_last_point != NULL )
      segments.push_back ( segment(*m_last_point,p) ) ;
    else
      throw std::logic_error ("must call set_origin() first!") ;

    *m_last_point = p ;
  }

  ...
} ;

I'd like to know your opinions.

Fernando Cacciola
Sierra s.r.l.
fcacciola_at_[hidden]
www.gosierra.com


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