I l@ve RuBoard Previous Section Next Section

1.5 Policies and Policy Classes

Policies and policy classes help in implementing safe, efficient, and highly customizable design elements. A policy defines a class interface or a class template interface. The interface consists of one or all of the following: inner type definitions, member functions, and member variables.

Policies have much in common with traits (Alexandrescu 2000a) but differ in that they put less emphasis on type and more emphasis on behavior. Also, policies are reminiscent of the Strategy design pattern (Gamma et al. 1995), with the twist that policies are compile-time bound.

For example, let's define a policy for creating objects. The Creator policy prescribes a class template of type T. This class template must expose a member function called Create that takes no arguments and returns a pointer to T. Semantically, each call to Create should return a pointer to a new object of type T. The exact mode in which the object is created is left to the latitude of the policy implementation.

Let's define some policy classes that implement the Creator policy. One possible way is to use the new operator. Another way is to use malloc and a call to the placement new operator (Meyers 1998b). Yet another way would be to create new objects by cloning a prototype object. Here are examples of all three methods:



template <class T>


struct OpNewCreator


{


   static T* Create()


   {


      return new T;


   }


};





template <class T>


struct MallocCreator


{


   static T* Create()


   {


      void* buf = std::malloc(sizeof(T));


      if (!buf) return 0;


      return new(buf) T;


   }


};





template <class T>


struct PrototypeCreator


{


   PrototypeCreator(T* pObj = 0)


      :pPrototype_(pObj)


   {}


   T* Create()


   {


      return pPrototype_ ? pPrototype_->Clone() : 0;


   }


   T* GetPrototype() { return pPrototype_; }


   void SetPrototype(T* pObj) { pPrototype_ = pObj; }


private:


   T* pPrototype_;


};


For a given policy, there can be an unlimited number of implementations. The implementations of a policy are called policy classes.[2] Policy classes are not intended for stand-alone use; instead, they are inherited by, or contained within, other classes.

[2] This name is slightly inaccurate because, as you will see soon, policy implementations can be class templates.

An important aspect is that, unlike classic interfaces (collections of pure virtual functions), policies' interfaces are loosely defined. Policies are syntax oriented, not signature oriented. In other words, Creator specifies which syntactic constructs should be valid for a conforming class, rather than which exact functions that class must implement. For example, the Creator policy does not specify that Create must be static or virtual—the only requirement is that the class template define a Create member function. Also, Creator says that Create should return a pointer to a new object (as opposed to must). Consequently, it is acceptable that in special cases, Create might return zero or throw an exception.

You can implement several policy classes for a given policy. They all must respect the interface as defined by the policy. The user then chooses which policy class to use in larger structures, as you will see.

The three policy classes defined earlier have different implementations and even slightly different interfaces (for example, PrototypeCreator has two extra functions: GetPrototype and SetPrototype). However, they all define a function called Create with the required return type, so they conform to the Creator policy.

Let's see now how we can design a class that exploits the Creator policy. Such a class will either contain or inherit one of the three classes defined previously, as shown in the following:



// Library code


template <class CreationPolicy>


class WidgetManager : public CreationPolicy


{


   ...


};


The classes that use one or more policies are called hosts or host classes.[3] In the example above, WidgetManager is a host class with one policy. Hosts are responsible for assembling the structures and behaviors of their policies in a single complex unit.

[3] Although host classes are technically host class templates, let's stick to a unique definition. Both host classes and host class templates serve the same concept.

When instantiating the WidgetManager template, the client passes the desired policy:



// Application code


typedef WidgetManager< OpNewCreator<Widget> > MyWidgetMgr;


Let's analyze the resulting context. Whenever an object of type MyWidgetMgr needs to create a Widget, it invokes Create() for its OpNewCreator<Widget> policy subobject. How ever, it is the user of WidgetManager who chooses the creation policy. Effectively, through its design, WidgetManager allows its users to configure a specific aspect of WidgetManager's functionality.

This is the gist of policy-based class design.

1.5.1 Implementing Policy Classes with Template Template Parameters

Often, as is the case above, the policy's template argument is redundant. It is awkward that the user must pass OpNewCreator's template argument explicitly. Typically, the host class already knows, or can easily deduce, the template argument of the policy class. In the example above, WidgetManager always manages objects of type Widget, so requiring the user to specify Widget again in the instantiation of OpNewCreator is redundant and potentially dangerous.

In this case, library code can use template template parameters for specifying policies, as shown in the following:



// Library code


template <template <class Created> class CreationPolicy>


class WidgetManager : public CreationPolicy<Widget>


{


   ...


};


In spite of appearances, the Created symbol does not contribute to the definition of WidgetManager. You cannot use Created inside WidgetManager—it is a formal argument for CreationPolicy (not WidgetManager) and simply can be omitted.

Application code now need only provide the name of the template in instantiating WidgetManager:



// Application code


typedef WidgetManager<OpNewCreator> MyWidgetMgr;


Using template template parameters with policy classes is not simply a matter of convenience; sometimes, it is essential that the host class have access to the template so that the host can instantiate it with a different type. For example, assume WidgetManager also needs to create objects of type Gadget using the same creation policy. Then the code would look like this:



// Library code


template <template <class> class CreationPolicy>


class WidgetManager : public CreationPolicy<Widget>


{


   ...


   void DoSomething()


   {


      Gadget* pW = CreationPolicy<Gadget>().Create();


      ...


   }


};


Does using policies give you an edge? At first sight, not a lot. For one thing, all implementations of the Creator policy are trivially simple. The author of WidgetManager could certainly have written the creation code inline and avoided the trouble of making WidgetManager a template.

But using policies gives great flexibility to WidgetManager. First, you can change policies from the outside as easily as changing a template argument when you instantiate WidgetManager. Second, you can provide your own policies that are specific to your concrete application. You can use new, malloc, prototypes, or a peculiar memory allocation library that only your system uses. It is as if WidgetManager were a little code generation engine, and you configure the ways in which it generates code.

To ease the lives of application developers, WidgetManager's author might define a battery of often-used policies and provide a default template argument for the policy that's most commonly used:



template <template <class> class CreationPolicy = OpNewCreator>


class WidgetManager ...


Note that policies are quite different from mere virtual functions. Virtual functions promise a similar effect: The implementer of a class defines higher-level functions in terms of primitive virtual functions and lets the user override the behavior of those primitives. As shown above, however, policies come with enriched type knowledge and static binding, which are essential ingredients for building designs. Aren't designs full of rules that dictate before runtime how types interact with each other and what you can and what you cannot do? Policies allow you to generate designs by combining simple choices in a typesafe manner. In addition, because the binding between a host class and its policies is done at compile time, the code is tight and efficient, comparable to its handcrafted equivalent.

Of course, policies' features also make them unsuitable for dynamic binding and binary interfaces, so in essence policies and classic interfaces do not compete.

1.5.2 Implementing Policy Classes with Template Member Functions

An alternative to using template template parameters is to use template member functions in conjunction with simple classes. That is, the policy implementation is a simple class (as opposed to a template class) but has one or more templated members.

For example, we can redefine the Creator policy to prescribe a regular (nontemplate) class that exposes a template function Create<T>. A conforming policy class looks like the following:



struct OpNewCreator


{


   template <class T>


   static T* Create()


   {


      return new T;


   }


};


This way of defining and implementing a policy has the advantage of being better supported by older compilers. On the other hand, policies defined this way are often harder to talk about, define, implement, and use.

    I l@ve RuBoard Previous Section Next Section