The Artima Developer Community
Sponsored Link

The C++ Source
Built-in Type Safety?
by Thomas Guest
June 25, 2005

<<  Page 3 of 4  >>

Advertisement

An Idea from Python

In Python, we might define a rectangle's initialiser as follows:
    class PyRect:
        '''Axis aligned rectangle'''

        def __init__(self, top, left, width, height)
            '''Initialise with the given position/dimensions'''
            ....
I have no particular reason for using width, height rather than right, bottom other than to indicate that there is no canonical way of specifiying an axis-aligned rectangle; if there were, the bug I'm discussing would have been less likely to trouble us.

We can now create PyRect objects:

    r1 = PyRect(0, 0, 288, 256)
    r2 = PyRect(top=0, left=0, width=288, height=256)
    r3 = PyRect(left=0, top=0, height=256, width=288)
Here, r1, r2 and r3 are equivalent. r2 and r3 are created using keyword argument syntax, which allows us to pass in parameters in the order we chose.

In C++, we can do something which superficially resembles this, allowing us to create Rectangles without fretting over parameter order.

    class Rectangle
    {
    public:
        /**
         * Default constructor builds an empty 
         * rectangle based at (0, 0).
         */
        Rectangle()  
        : top(0), left(0), width(0u), height(0u)
        {
        }
        
        /**
         * Set the rectangle top coordinate.
         * @param new_top
         * @returns a reference to self (for use
         * in method chaining).
         */
        Rectangle & top(int new_top)
        { 
            top = new_top;
            return *this;
        }
        
        Rectangle & left(int coord)
        .... 
                  
    private:              
        int top, left;
        unsigned width, height;
    };
We can construct objects of this class as follows:
    Rectangle r1 = Rectangle()
                       .height(256)
                       .width(288);
    
    Rectangle r2 = Rectangle()
                       .top(0)
                       .left(0)
                       .width(288)
                       .height(256);

    Rectangle r3 = Rectangle()
                       .width(288)
                       .height(256);
This technique is known as the Named Parameter Idiom [3].

An Idea from Smalltalk

Alternatively, we can adopt an idea originally documented in Smalltalk [4] and create wrapper types for the parameters required by the Rectangle constructor:
    class Xcoord 
    {        
    public:
        explicit Xcoord(int x);
    ....
    };
Having set up similar wrapper classes for Ycoord, Width, Height, we declare the Rectangle constructor:
    class Rectangle
    {
    public:
        Rectangle(Xcoord x,
                  Ycoord y,
                  Width w,
                  Height h);
    ....
    };
and the C++ type-system is once again working for us:
    Rectangle const full_screen(Xcoord(0),
                                Ycoord(0),
                                Width(288),
                                Height(256));
This technique is known as the Whole Value Pattern.

Note that with our strongly type-differentiated arguments we could, if we wanted, overload the constructor, allowing clients to supply parameters in a different order.

True Typedefs

A problem with the Whole Value Pattern approach is that we may end up creating different classes which essentially do the same thing (that is, wrap one of the built-in integral types). The Xcoord, Ycoord, Width and Height classes all need to implement the same arithmetical operators, and they must all allow access to the base value; in effect all we have done is replicate the same class to give it four distinct types.

(Note that if this really does turn out to be the case, then we've probably failed to identify the values used by our program correctly: the Whole Value Pattern isn't just about type-safety, it's also about identifying and properly encapsulating the separate value-based types in our problem domain.)

This problem is discussed in more depth in an article by Mark Radford [5], which identifies three candidate mechanisms for generating the required class families:

The STLSoft library [6] implements the second mechanism with its true_typedef class template [7], which allows us to define our wrapper classes as follows:

    #include <stlsoft/true_typedef.hpp>
    
    // Tag types
    struct Xcoord_t;
    struct Ycoord_t;
    struct Width_t;
    struct Height_t;
    
    typedef stlsoft::true_typedef<int, Xcoord_t> Xcoord;
    typedef stlsoft::true_typedef<int, Ycoord_t> Ycoord;
    typedef stlsoft::true_typedef<unsigned, Width_t> Width;
    typedef stlsoft::true_typedef<unsigned, Height_t> Height;
Here, declaration and use of the Rectangle constructor remain as before; the STLSoft header takes care of the rest.

Class Redesign

Even without named parameters, whole values or true typedefs, we can improve on things.

I mentioned before that there is no canonical way to construct a rectangle. There is, however, a canonical way to construct a point on a display screen— that is, to supply its X and Y coordinates, in that order. So, if we write:

 Point const bottom_right(288, 256); 
we can be confident our Point really will be at the bottom right hand corner of the screen.

Instead of using coordinates and coordinate differences, we might, then, prefer to construct our Rectangle from two of its corners:

    class Rectangle
    {
    public Rectangle(Point const & top_left_corner,
                     Point const & bottom_right_corner);

    ....
    };
Anyone with a basic grasp of geometry will realise that if we wish to define a rectangular region on-screen from two corners, then those two corners must be diagonal opposites—just think of creating a rectangle in a graphical drawing package by anchoring one corner then dragging the other corner into position. There are eight permutations:
  • (Top Left, Bottom Right),
  • (Bottom Right, Top Left),
  • (Top Right, Bottom Left),
  • etc.
Of these, the first is the outstanding favourite, even without looking at the class documentation. What's more, a little simple logic in the Rectangle constructor allows it to make sense of the other seven, perhaps outputting a warning if it does not receive the expected pair.

So, then, although there is no one standard way to construct a rectangle, one of the standard ways is less vulnerable to misuse!

<<  Page 3 of 4  >>


Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2017 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us