The Curate's Wobbly Desk

The Curate's Wobbly Desk

By Phil Bass

Overload, 13(70):, December 2005


The Vicar's Lodger

The story of the curate's egg is well known [ story ], but I bet you've never heard about the curate's wobbly desk.

When Aubrey Jones was first ordained he was appointed curate to St Michael's church, Belton Braces. It was a small parish serving just a few quiet hamlets and there would have been nowhere for Mr. Jones to stay if he had not been offered lodgings at the vicarage. The vicar, the Reverend Cuthbert Montague-Smith, was a large and imposing man with strong opinions on how to do God's bidding. The timid Aubrey Jones found him rather... well, intimidating.

The bishop had suggested that Aubrey would benefit from studying the early history of the Christian church and the vicar expressed a hope that this research would turn up some interesting material for the parish magazine. Eager to please, Aubrey went out and bought a cheap, mass-produced, self-assembly desk to work at. The D.I.Y. shop was running a special offer - free delivery - and the curate felt he was getting a bargain.

Secret Drawers

Reading through the assembly instructions Aubrey was delighted to find that the desk contained a secret drawer. He had fond notions of keeping a diary and one day, perhaps, writing a book on the life of an English pastor based on his own ministry and experiences. But he suspected that Cuthbert would regard such activities as of little practical value and it would be better for the vicar to remain ignorant of those particular jottings.

As chance would have it, the vicar's desk also had a secret drawer. I don't know what Cuthbert kept in that drawer, or even if he knew it was there. But I do know that both Aubrey's and Cuthbert's secret drawers were operated by two catches. If you press both catches at the same time the drawer slides out. I remembered the vicar, the curate and the secret drawers when I was working on the software for a computer game. (It would have been called "Murder at the Vicarage", but Agatha Christie got there first.) "Let's use that in the game", I thought, and wrote an interface class representing the secret drawer based on classes from my Event/Callback library [ Bass ]. Listing 1 shows the Event class template used for the drawer release mechanism and the Callback::Function class template used for the catches.

// Event classes.
template< typename Arg >
struct Event : std::list< Callback::Function<Arg>* >
{
  typedef Arg                     argument_type;
  typedef Callback::Function<Arg> callback_type;
  void notify( Arg arg )
  {
    std::for_each( this->begin(), this->end(),
       bind_2nd(
          memfun( &callback_type::operator()),
          arg ));
  }
};
// Callback function base classes.
namespace Callback
{
  template< typename Arg >
  struct Function
  {
    typedef Arg argument_type;
    virtual ~Function() {}
    virtual void operator() ( argument_type ) = 0;
  };
}

Listing 1 - the Event and Callback::Function classes.

The Drawer class interface is shown in Listing 2. The key feature here is that Digital_Input is an abstract base class with a pure virtual function call operator. When catch A's Digital_Input callback is invoked the lock/unlock state for catch A must be updated, the state of catch B must be read and, if both catches are in the unlock position, the drawer release mechanism must be activated by triggering the Digital_Output event. The overall effect is that the Drawer class behaves like an And gate in an electronic circuit.

// A value type with just two values.
enum Digital_Value { off, on };

// Input and output types.
typedef Callback::Function< Digital_Value >
   Digital_Input;
typedef Event< Digital_Value > Digital_Output;

// A secret drawer operated by two catches.
class Drawer
{
public:
    Drawer();
    Digital_Input& catch_A();  // catch A
    Digital_Input& catch_B();  // catch B
    Digital_Output& output();  // drawer release
private:
    . . .
};

Listing 2 - Secret drawer interface.

In the game, a Drawer object is created and GUI push-button widgets are attached to the drawer's inputs (the catches). The drawer itself is represented by an Image widget which shows either a closed drawer or an open drawer. A callback that toggles between the open and closed images is attached to the drawer's output (the drawer release mechanism). The player has to find a way of pressing both of the catches at the same time to open the drawer - not easy using just a mouse. A rough sketch of the client code is shown in Listing 3.

// Internal objects.
Drawer drawer;                       // secret drawer

// User interface.
GUI::Button  catch_A, catch_B;       // buttons to release the drawer
GUI::Image   picture;                // picture of open/closed drawer
Toggle_Image toggle_image(picture);  // functor that toggles open/closed

// Connect the UI widgets to the internal objects.
catch_A.push_back( &drawer.catch_A() );
catch_B.push_back( &drawer.catch_B() );
drawer.output().push_back( &toggle_picture );

// Run the GUI scheduling loop.
GUI::run();

Listing 3 - Using the Drawer class.

The rest of this paper describes two ways of implementing the Drawer class with the help of our curate, his vicar and their furniture.

The Curate's Self-Assembly Desk

As soon as he had a spare half-hour Aubrey Jones opened the box containing his flat-pack desk, carefully laid out the panels, runners, feet, dowels, nuts and bolts, and began to assemble them. He paid particular attention to the secret drawer. The drawer itself had a grain-effect finish that looked remarkably like real wood, but probably wasn't. The release mechanism was an integral part of the drawer, located at the back. The secret catches were separate - metal, with knobs in the same fake wood as the drawer and disguised as decorative features. The catches had to be fastened to the front of the drawer and connected to the release mechanism with two long, thin and worryingly flimsy metal rods.

The structure of my code was very similar, as you can see from the overview in Listing 4 and the function bodies in Listing 5. The drawer release mechanism was represented by Digital_Value and Digital_Output members of the Drawer class. The catches were separate classes ( Catch_A and Catch_B ) and they were attached to the Drawer class by pointers. With this design the functions in the public interface are trivial and shown here defined within the class declaration.

class Drawer
{
public:
  Drawer();
  Digital_Input& catch_A() { return catch_A_input; }
  Digital_Input& catch_B() { return catch_B_input; }
  Digital_Output& output() { return output_event; }
private:
  struct Catch_A : Digital_Input
  {
    Catch_A( Drawer* d ) : value( off ), drawer( d ) {}
    void operator()( Digital_Value );
    Digital_Value value;
    Drawer* const drawer;
  };
  struct Catch_B : Digital_Input
  {
    // . . . same as Catch_A . . .
  };
  void sync();    // set output value from inputs
  Catch_A        catch_A_input;  // catch A's lock/unlock state
  Catch_B        catch_B_input;  // catch B's lock/unlock state
  Digital_Value  output_value;  // drawer release state
  Digital_Output output_event;  // drawer release event
};

Listing 4 - Overview of the Drawer with separate Catch classes.

// Operate Catch A
inline void Drawer::Catch_A::operator()( Digital_Value value )
{
  drawer->catch_A_input.value = value;
  drawer->sync();
}
// Operate Catch B
inline void Drawer::Catch_B::operator()( Digital_Value value )
{
  drawer->catch_B_input.value = value;
  drawer->sync();
}
// Create the Drawer
inline Drawer::Drawer()
  : catch_A_input( this ), catch_B_input( this ), output_value( off )
{}
// Set and publish the state of the drawer release mechanism
inline void Drawer::sync()
{
  output_value = 
     Digital_Value( catch_A_input.value & catch_B_input.value );
  output_event.notify( output_value );
}

Listing 5 - Implementation of the Drawer with separate Catch classes.

This design is conceptually simple, but it didn't feel quite right. Like cheap, mass-produced furniture it seemed inelegant and unsatisfying. Did the Catch classes really have to store a pointer to their parent object? After all, the address of the Drawer object is a fixed offset from each of the Catch objects. Couldn't we just subtract that offset from the Catch object's this pointer and apply a suitable cast?

After some thought I decided that pointer arithmetic and casting would be worse than the disease I was trying to cure. A case of premature optimisation, and an ugly one at that. I needed to think like the master craftsmen of old. And that reminded me of the vicar's desk.

The Vicar's Antique Writing Table

Cuthbert Montague-Smith loved his big sturdy old desk. It was reminiscent of the magnificent library writing table at Harewood House, near Leeds [ chippendale ]. Cuthbert suspected it was built by Thomas Chippendale himself, although he was unable to provide a shred of evidence to support that view.

I don't suppose the great furniture maker would appreciate the finer points of software design in C++, but I tried to imagine the approach he would use. He would surely pay considerable attention to detail and not rest until he had discovered a method that was both elegant and practical.

With this in mind I thought again about the Drawer class implementation. The curate's desk design in Listings 4 and 5 contains Catch classes that reference an external object (the Drawer itself); that is why it needs those inelegant pointers. If we could move the external data into the Catch classes the pointers would not be necessary. So the question is, how can we make the Drawer state variables part of two separate Catch objects?

It's no good putting member variables into the concrete Catch classes because that would just duplicate the data; and we can't put data into the Digital_Input class because that would compromise the Event/Callback library. The only option is to put them in a shared base class. The key to the desk is virtual inheritance. [ 1 ] Listing 6 and Listing 7 show how I chose to use this technique.

class Drawer
{
public:
  . . .
private:
  struct Data
  {
    Data();
    void sync();
    Digital_Value  catch_A_value,
                   catch_B_value;
    Digital_Value  output_value;
    Digital_Output output_event;
  };
  struct Catch_A : Digital_Input, virtual Data
  {
    void operator()( Digital_Value );
  };
  struct Catch_B : Digital_Input, virtual Data
  {
    void operator()( Digital_Value );
  };
  struct Body : Catch_A, Catch_B {} body;
};

Listing 6 - Overview of the Drawer using virtual inheritance.

// Operate Catch A
inline void Drawer::Catch_A::operator()(
   Digital_Value value )
{
  catch_A_value = value;
  sync();
}
// Operate Catch B
inline void Drawer::Catch_B::operator()(
   Digital_Value value )
{
  catch_B_value = value;
  sync();
}
// Initialise the Drawer's state
inline Drawer::Data::Data()
   : catch_A_value( off ),
   catch_B_value( off ),
   output_value( off )
{}
// Set and publish the state of the drawer 
// release mechanism
inline void Drawer::Data::sync()
{
  output_value = Digital_Value(
     catch_A_value & catch_B_value );
  output_event.notify( output_value );
}

Listing 7 - Implementation of the Drawer using virtual inheritance.

All the variables have been moved to the private nested class, Data . As this is an implementation detail of the Drawer class I have not bothered to create separate interface and implementation sections for Data . Purists can add a private section and suitable access functions if they wish. It is appropriate, however, to provide Data with a constructor and a function that sets the drawer release mechanism's state from the Catch values to avoid duplicating these operations in both the Catch classes.

The Catch classes themselves use virtual inheritance to "import" the shared Data object. They also provide a function that updates their own lock/unlock state, calculates the drawer release state and publishes the drawer release state to the client code.

The Drawer class could inherit directly from the Catch classes, but that would mean exposing the Data class and the concrete Catch classes to its clients. Instead, I have chosen to write a nested Body class that completes the virtual inheritance diamond and then store a Body member within the Drawer class. That way, none of the classes used in the Drawer implementation pollute the namespaces used in the client code.

The Catch class member functions in the vicar's desk design are slightly simpler than those in the curate's version. Moving the data to a base class enables them to access the variables directly instead of via those ugly and unnecessary pointers. Initialisation of the data members is also slightly simpler because there are no pointer values to set (which means one less opportunity for a bug). And the sync() function is the same in both designs. Chippendale would have been proud.

It All Falls Apart

The curate struggled to assemble his desk. The instructions seemed to have been translated from a foreign language (badly), the diagrams didn't seem to match the parts he had and Aubrey Jones' mind wasn't good at 3D visualisation. The release mechanism for the secret drawer, in particular, baffled the poor man. Eventually, the desk was completed and moved into position under the window of Aubrey's bed sitting room where he could look out over the garden.

The desk was always a bit wobbly and the secret drawer never did work. (The connecting rods were not installed correctly, so the drawer release would not activate.) Aubrey never quite found the time to research the early history of the Christian church and with no place to hide his private writings he soon lost the urge to keep a diary. Indeed, after a few years as the vicar of a provincial parish on the borders of London and Essex his ecclesiastical career took a turn for the worse [ genesis ]. He may yet write a book. If he does, it will be about temptation, greed and the frailty of man, but sadly it will not be about a life of service to the church.

References

[story] See http://www.worldwidewords.org/qa/qa-cur1.htm , for example.

[Bass] Phil Bass, "Evolution of the Observer Pattern", Overload 64, December 2004.

[chippendale] http://www.harewood.org/chippendale/index2.htm

[genesis] http://www.lyricsfreak.com/g/genesis/58843.html



[ 1 ] I claim poetic licence for bending the rules of English grammar here.






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.