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.
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.
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.
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.
The rest of this paper describes two ways of implementing the Drawer class with the help of our curate, his vicar and their furniture.
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.
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.
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. Listing 6 and Listing 7 show how I chose to use this technique.
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.
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.
[story] See http://www.worldwidewords.org/qa/qa-cur1.htm, for example.
 I claim poetic licence for bending the rules of English grammar here.
Overload Journal #70 - Dec 2005 + Programming Topics
|Browse in :||
All > Topics > Programming (768)
Any of these categories - All of these categories