Exploring Patterns Part 2

Exploring Patterns Part 2

By Francis Glassborow

Overload, 6(27):, August 1998


Before I focus on the pattern for this issue I want to tell you about something that I learnt whilst doing further research for the Singleton pattern.

As most of you know, declaring a copy constructor for a class inhibits the compiler from generating its own. After considerable pressure from several people (I think I was among the most vociferous) WG21 & J16 specified that all the following were classified as copy-constructors (for the sake of example, I am assuming that I am dealing with a class MyType ):

MyType(MyType &);
MyType(MyType const &);
MyType(MyType volatile &);
MyType(MyType const volatile &);

In addition all constructors with a first parameter matching one of those four and defaults for all the other parameters will also be copy constructors. The clarification concerned whether the volatile qualified parameters resulted in inhibiting the compiler from generating a copy constructor or a default constructor. Note my wording, I fondly believed that any constructor would only inhibit one or other but not both of the possible compiler generated ones. This believe seems to be shared by many good, even expert, writers. On numerous occasions I have had cause to point out the flaw in code such as:

class MyType
{
  MyType();
public: 
  // whatever
};

where the clear intent is that it should not be possible to create public instances of MyType . The flaw is due to a quirk of the grammar for declarations that results in the following code being syntactically correct (though usually hiding undefined behaviour because storage is copied before it has been initialised):

  Mytype mt = mt;

It is the quirky grammar for initialisation in this form that leads me to strongly recommend that you always use the function form for user defined types. If you wrote:

  MyType mt(mt);

You would get a compile time error unless an mt of appropriate type had been declared in an outer scope. In other words, always call a constructor explicitly rather than use an implicit constructor and call to the copy constructor. Actually, as more programmers recognise that object types (as opposed to value types) should not have publicly accessible copy constructors the problem will occur less often.

Back to the main point. Unless you have declared one of the copy constructor forms, the compiler is at liberty to attempt to generate one of the form MyType(Mytype const &) .

However when I have raised the issue of the missing declaration of a private copy constructor, all I have ever had is ' Ahh… I missed that ' and the writer has added a copy constructor. None of them has ever removed their declaration of a default constructor. I think, that like me, many of them have always thought in terms of two almost disjoint sets of constructors, copy constructors and non-copy constructors (with an overlap caused by defaulting parameters so as to leave a more general constructor useable as a copy constructor). Even though I have explicitly made that statement (about two disjoint sets) in the presence of some of the World's greatest C++ experts, none have ever corrected me (perhaps some of them just heard what they expected, and some were too polite - though I doubt it, that kind of politeness is unhelpful).

In fact, declaring a copy constructor inhibits the compiler from generating a default constructor as well as any other copy constructor. This means that if you want to prevent public creation of a type all you need to do is to declare a private copy constructor. For example:

class MyType
{
  MyType(Mytype &);
public: 
  // whatever
};

correctly does what was intended by the earlier flawed case.

So we see that the correct idiom for a class that must not be publicly constructable is to declare a private copy constructor.

Now we also need to know what to do if we want to prevent copying but are happy to allow default construction (needed for example if you are intending to provide dynamic arrays of the type - but not the STL containers that require access to a copy constructor). The fix is easy:

class MyType
{
  MyType(Mytype &);
public: 
  MyType(){};
  // whatever
};

That default constructor with an empty body tells the compiler to do exactly what it would have done had it been able to generate a default constructor for itself.

The Visitor Pattern

When you read the following please do not take my word for it, wait until the experts have had a look and either confirm, extend or correct my interpretation. Think of this as an essay being read by a student at a seminar, only after the discussion is complete will those involved know confidently what is true and what is not.

In general we are concerned with providing stable, well-defined behaviour for our abstractions. At the same time we want to reserve the right to change implementation details. This is the main motive for the concept of private / protected / public interfaces. However there are cases where we have a clear idea about how we wish to provide our data but wish to reserve the right to alter behaviour.

One way of tackling this problem is by using a base class to provide the data (together with protected read and write member functions) and using derived classes (or classes with pointers to the desired data structure) to provide the behaviour. This works well where we have a single fundamental data type but it does not work when we need a hierarchy. One example given (in Design Patterns) of such a hierarchy is that of the different node types required for a parse tree used by a compiler for a computer language. For example in C the node for an assignment must provide a left pointer to an expression node that evaluates as a modifiable lvalue and a right pointer to an expression node that evaluates to an rvalue. Arithmetic operator nodes need left and right pointers to nodes for expressions evaluating to rvalues.

While the data requirements are very stable (fortunately computing languages infrequently add features requiring new node types) the desired behaviour can change. Indeed the desired behaviour will depend on exactly what you are trying to do(compile, optimise etc.). It is usually unwise to have an interface cluttered with a large number of member functions, particularly if these are only tenuously related to each other. Even if we could provide an exhaustive list of all the behaviour we want in such a class hierarchy it is probably a poor idea to provide it in the classes.

The idea behind the Visitor pattern is to allow programmers to encapsulate coherent behaviour across a number of classes (not necessarily even in the same hierarchy) into a single class. Typically, we have something like:

class A_type;
class B_type;
class C_type;
// etc.

These declarations can be replaced with definitions and the various classes are usually part of a hierarchy but this is not necessary for the pattern to work.

class MyVisitor
{
public:
  virtual MyVisitor& 
      VisitorToA_type(A_type *) = 0;
  virtual MyVisitor& 
      VisitorToB_type(B_type *) = 0;
  virtual MyVisitor& 
      VisitorToC_type(C_type *) = 0;
  // etc
  virtual ~MyVisitor() throw() {};
};

This is an abstract base class from which concrete classes providing single behaviours can be derived. Actually all the functions could share the same function name as overloading would resolve which one to use. Whether you use function overloading or not is a matter of style (shorter names requiring thoughtful reading versus longer names providing specific information). If the visitor is not intended to mutate (change the state of) the host objects then the parameters in the above should be of type const * .

Each of the classes to be visited must include a member function of the form:

MyVisitor & host(MyVisitor &);

Again, const qualification should be used as appropriate: const member function if the Visitor is non-mutating and const qualified parameter if the Visitor is not mutated by visiting.

The body of host() depends upon the class in which it is placed so that it calls the appropriate member function of the Visitor. For example:

MyVisitor & A_type::host(MyVisitor & v)
{ 
  return v.VisitorToA_type(this);
}

If you have chosen the function overloading mechanism then all host types will have apparently identical bodies (though the type of 'this' will select the correct overload version from the visitor's members functions):

MyVisitor & A_type::host(MyVisitor & v)
{
  return v.Visitor(this);
}

The return type of the host functions and the members of the visitor could be void but I have a strong preference for returning an object for possible reuse. It doesn't cost much but provides one extra resource for those that wish to use it.

Let me offer a trivial example where you want to be able to dispatch the data to some form of output. You would write something such as:

class StoreData: public MyVisitor
{
  istream & output;
  // inhibit copying
  StoreData(StoreData const &);
  void operator = (StoreData const &);
public:
  explicit StoreData(istream & out = cout) : output(out) {}
  virtual MyVisitor& 
      VisitorToA_type(A_type *);
  virtual MyVisitor& 
      VisitorToB_type(B_type *) ;
  virtual MyVisitor& 
      VisitorToC_type(C_type *) ;
  // etc
  virtual ~StoreData() throw() {};
};

Now we come to a problem. The bodies of the member functions of StoreData require access to the specific data of the host classes. This means that each of these classes must provide public access functions for its data (this does not break encapsulation but it does restrict the owners of the host classes (but remember that a pre-condition for the use of the Visitor pattern is stable data structures).

Remember that the advantage of the Visitor pattern is that you can retrofit behaviour to a bunch of (usually related) classes. Of course, you can install any specific behaviour into the classes themselves, but one major advantage of the Visitor pattern is that it supports extensible behaviour.

It also works well where you have collections of objects, or even composites of heterogeneous objects. Design Patterns gives the example of something like a piece of equipment (such as a computer) that is built from components that are themselves built from components etc. If you want to cost such equipment you could (if you thought far enough ahead) provide a visitor object that was passed around collecting cost information from each sub-component (to do that it would have to visit each sub-sub component recursively). This may sound complicated until you realise that all that is required is that the host() function of a component dispatches the visitor to each of the sub-components before calling the specific member function of the visitor object on itself.

I think that the Visitor pattern is a powerful program technique that deserves to be widely known. If you are serious about software development you should work through at least one implementation of Visitor to ensure that you understand it and will remember to provide the groundwork where it has potential use. For example, the Harpist's Hotel project might benefit from a Visitor facility in all classes that provide charges (a bill is made up from a variety of costs that are certainly not all from objects in the same hierarchy; think about meals and rooms.) Of course this problem has many other solutions (such as ensuring that each object includes a reference/pointer to a bill object) and the lack of the need for extensible behaviour probably makes other methods more appropriate.

Before I wrap this up I want to speculate a bit on the possibility of avoiding the need for public read/write access functions for data in host classes.

Keeping Data Out of Reach

The first thought when tackling this problem (restricting access to data) is to consider using friendship. Unfortunately there is no mechanism for granting friendship to a hierarchy of classes and part of the fundamentals of the design of the Visitor pattern seems to require a hierarchy. We need a base class so that the parameter of the host() functions can provide polymorphic behaviour (select the type of behaviour that the visitor is going to add). But that does not mean that we need a hierarchy of derived types (to which friendship can only be granted on a one by one basis, which would rather defeat the object of the exercise).

We have another possibility via templates:

class MyVisitor
{
public:
  virtual MyVisitor& 
      VisitorToA_type(A_type *) = 0;
  virtual MyVisitor& 
      VisitorToB_type(B_type *) = 0;
  virtual MyVisitor& 
      VisitorToC_type(C_type *) = 0;
  // etc
  virtual ~MyVisitor() throw() {};
};

enum Operation {Op1, Op2};

// to provide a tool for instantiating template classes
template <Operation op> class Guest : public MyVisitor
{
  // inhibit copying
  Guest(Guest const &);
  void operator = (Guest const &);
public:
  ~Guest() throw() {}
};

// Note this is still an Abstract Base Class and so instances 
// must be specialised, or be used as ABC's
// Here is an example of a specialisation
template <> class Guest<Op1>
{
  istream & output;
public:
  explicit Guest(istream & out = cout) : output(out) {}
  MyVisitor& VisitorToA_type(A_type *);
  MyVisitor& VisitorToB_type(B_type *) ;
  MyVisitor& VisitorToC_type(C_type *) ;
  // etc
};

Unfortunately, though templates can declare friends and ordinary classes can declare instances of templates to be friends there is no syntax available to declare a template class as a friend. So this idea may be interesting but it does not solve the problem of finding a way whereby the various host classes can provide privileged access rights to a Visitor hierarchy.

My next idea was to try and increase the security by using namespaces coupled with fuzzing the types used (remember that the data types/structures for the host classes must be stable if we are using the Visitor pattern.) This is my first attempt:

namespace ControlAccess
{
  typedef int Int; // just as an example
  class Object
  {
    Int value;
  public:
    Object(int i=0):value(i){}
    void set_value(Int v){value = v;}
    Int get_value(){ return value;}
  };
}

int main()
{
  ControlAccess::Object obj;
  obj.set_value(3);
  cout << obj.get_value();
  return 0;
}

I hoped that by hiding the typedef in a namespace that its implicit use outside the namespace would create an error. I agree that this was a pretty vain hope because a typedef does not create real type. Of course this code compiled.

So next I tried replacing the typedef by a real type:

class Int
{
  int value;
public:
  Int(int i=0):value(i){}
  operator int () {return value;}
};

Unfortunately, the so called Koenig lookup allows the compiler to find the Int type in the context of obj.set_value(3) and obj.get_value() . I had one last shot in my locker (remember that my purpose is to make it possible to force programmers to think about using the access functions they have in host classes). Consider:

class Int
{
  int value;
public:
  explicit Int(int i=0):value(i){}
  int convert_to_int () {return value;}
};

Now the two function calls in question fail and require an explicit cast (for seti ) and a call to convert_to_int (for geti) to make the compiler happy.

So, if there is data in a host class that you are reluctant to make easily accessible to the world at large, but that you do need to make available to Visitor, you can add this extra layer. Visitors will have to use this as well, but we are assuming that we are catering for data that needs thoughtful use.

Conclusion

I have learnt a lot while putting this article together, and I know I have ranged further afield than strictly required for the topic. However, I think some of the ideas and failed attempts may prove instructive. What I do know is that the process of active exploration rather than passive reading is what provides the value to me. I think the same will apply to you.

Postscript

It has just occurred to me that there is one other mechanism available. Have each host class that has data that you do not want to make generally accessible declare the visitor ABC (MyVisitor for example) a friend. Now you can place those access functions that you want to restrict in the protected interface of the ABC. That way all the concrete visitors will have the access they need but no-one else will. Here is some code by way of example.

class MyVisitor;
// sample host classes
class First
{
  friend class MyVisitor;
  int val;
public:
  First(int i=0):val(i){}
  MyVisitor & host(MyVisitor &);
};

class Second
{
  friend class MyVisitor;
  float val;
public:
  Second(float f=0.0):val(f){}
  MyVisitor & host(MyVisitor &);
};

class Aggregate
{
  First f;
  Second s;
  int val;
public:
  Aggregate(int v=2, 
            int first = 1, 
            float second=1.0)
    :f(first), s(second), val(v){}
  MyVisitor & host(MyVisitor &);
  int geti(){return val;}
  void seti(int i){val = i;}
};

// now the visitor base class
class MyVisitor
{
protected:
  int getFirstval(First & f){return f.val;}
  void setFirstval(First & f,int i){f.val=i;}
  float getSecondval(First & f){return f.val;}
 void setSecondval(First &f,float i){f.val=i;}
public:
  virtual MyVisitor& visitFirst(First *) = 0;
  virtual MyVisitor& visitSecond(Second *) =0;
  virtual MyVisitor& visitAggregate(Aggregate *) = 0;
  virtual ~MyVisitor() throw() {};
};

class PrintData: public MyVisitor
{
public:
  MyVisitor& visitFirst(First *);
  MyVisitor& visitSecond(Second *);
  MyVisitor& visitAggregate(Aggregate *);
  ~PrintData()throw(){}
};

The implementation of the three member functions might go like this:

MyVisitor& PrintData::First(First * f)
{
  cout<< "First is " << getFirstval(*f);
  return *this;
}

MyVisitor& PrintData::Second(Second * s)
{
  cout<< "Second is "<< getSecondval(*s);
  return *this;
}

MyVisitor& PrintData::Aggregate(Aggregate * a)
{
  cout << "Aggregates own data is:" << 
                               a.geti();
}

And the implementations of the host functions are:

MyVisitor & First::host(MyVisitor & v)
{
  v.visitFirst(this);
  return v;
}

MyVisitor & host(MyVisitor & v)
{
  v.visitSecond(this);
  return v;
}

MyVisitor & host(MyVisitor & v)
{
  f.host(v);
  s.host(v);
  v.visitAggregate(this);
  return v;
}

And finally a very short program to use this:

int main()
{
  Aggregate test;  // use defaults
  PrintData pd;
  test.host(pd);
  return 0;
};

The above code is untested so it is up to you to debug it, in doing so you will need to understand it.

Post-Postscript

Before writing this article I had thought there was a way of declaring a template class a friend. When I failed to get my code to compile I checked with a couple of UK C++ experts who opined that it was not possible, hence the assertion. I have now had a chance to check the FDIS and find that my original belief is justified though the syntax is counter-intuitive.

The inclusion of the line:

template<Operations> friend class Guest;

in each host class should provide the desired access. However, I cannot find a compiler to compile it. Anyway, I believe that my postscripted solution is technically better as well as being compilable with the current compiler releases.






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.