Click here to Skip to main content
15,881,172 members
Articles / Programming Languages / C++
Article

Using Class Templates with Fake Parameters in Derived Classes.

Rate me:
Please Sign up or sign in to vote.
3.70/5 (7 votes)
21 Aug 20079 min read 51.3K   121   14   13
An article provide new C++ idom to avoid code duplication.

Introduction

Combining templates and inheritance opens new ways for creating power techniques in C++ [1-3]. This article presents a new technique to avoid code duplication in some special cases and provides examples to illustrate it. The technique is very simple, and it is surprising that it has not yet been published.

Motivation

The main motivation behind this technique is to provide a new way to avoid code duplication.

Definition

We will follow standard template terminology. A reference for this terminology can be found in [4,§45].
Note the distinction between the terms class template and template class:

Class template is a template used to generate template classes. You cannot declare an object of a class template.

Template class is an instance of a class template.

Starting point

Consider the following simple construction:

C++
template <class T>
class Derived :   public Base           (1)

The class template Derived can consist of any member functions, members, static data members, and so forth, but the members of the class do not depend on the template parameter T in any way. So, in a sense, T is a fake parameter and simply can be omitted:

C++
template <class>
class Derived :   public Base           (1-a)

For any specialization of the class Derived, we need to provide a specific parameter T. The parameter itself does not play any role; it is used only for specialization and in that case it will simply be an empty structure:

C++
struct Derived1_Tag                      (2)
{};

The name of the type reflects that the fake parameter resembles a Tag. The next (optional) step is to provide some naming convention to connect the Tag and the template specialization. We will associate the tag name with the name of the template class. One of the easiest ways to do this by using typedefs:

C++
typedef  Derived<Derived1_Tag>       <derived1_tag />  Derived1;              (3)

Thus, the template argument includes the name of the derived template class by convention. We will name the idiom (1-3) as TFP idiom (Template with a fake parameter) for the rest of the article.
That is all that the technique consists of. It is very simple, but next we will see when and how it can be used.

Using the Technique

This technique can be used in the following cases:

  1. A set of derived classes that is all identical except for class name.
  2. A set of derived classes that differ slightly from each other in terms of behavior.

Let's look at the second case. Suppose we have a base class:

C++
class Base 
{
    //……
    virtual void foo1() =0;
    virtual void foo2() =0;
    //…..
};

And 3 derived classes:

C++
class Derived1 : public Base 
{
    //…….
    virtual void foo1();
    virtual void foo2();
    //…….    
};
    class Derived2 : public Base 
{
    //…….
    virtual void foo1(); //implementation is the same as in Derived1
    virtual void foo2();
    //…….    
};
    class Derived3 : public Base 
{
    //…….
    virtual void foo1();
    virtual void foo2(); // implementation is the same as in Derived1
    //…….    
};

The other details are omitted, but suppose the derived classes are the same except that they have different implementations of the functions foo1() and foo2().

In that case, we follow these steps:

Step 1: Replace Derived classes with a class Template with fake parameters. As a default implementation for function foo1() and foo2() take the implementation of the class Derived1:

C++
template<class><class />
class Derived : public Base 
{
    //…….
    virtual void foo1();  //class Derived1 implementation
    virtual void foo2(); // class Derived1 implementation
    //…….    
};

Step 2: Define empty structs for all Derived classes.

C++
struct Derived1_Tag
{};

struct Derived2_Tag
{};

struct Derived3_Tag
{};

Step 3: (optional, but useful) Using typedef, provide user-friendly names for the classes:

C++
typedef Derived< Derived1_Tag> Derived1;
typedef Derived< Derived2_Tag> Derived2;
typedef Derived< Derived3_Tag> Derived3;

Step 4: Specialize Derived functions that are different for the classes.

C++
template <>
void  Derived2::foo1()
{
    //…implementation
}

template <>
void  Derived3::foo2()
{
    //…implementation

}

The interesting detail is that the specialization of member functions plays the same role as overriding.

Let's me stress that it is legal to specialize single member function without specializing all member functions.
According to the C++ Standard [5,§14.7.3] :
"A member function, a member class or a static data member of a class template may be explicitly specialized for a class specialization that is implicitly instantiated."

If you have problem with template terminology such as "explicit instantiation","implicit instantiation", "partial specialization" and so forth, please consult [4,§45-48]. If you do not want to go deeper, just remember that the standard allows you to specialize some of the member functions without specializing all of them.


Let's illustrate the technique with some more examples.

Replacing Macros

As a first example, we will consider a class Exception. This idea is taken from Xerces library [6] and the class is simplified it for the purposes of illustration.

Let us observe how exceptions are implemented in the library. They provide one Base class like this (the real implementation of Xerces Exception is different):

C++
class BaseException
{
 public: 
  BaseException(); 

  BaseException(const std::string &what);
  
  virtual ~BaseException() = 0;
  
  BaseException(const BaseException& e);
  
  BaseException& operator=(const BaseException& e);

  virtual const char* what() const;
  
  private:
    std::string  what_;
};

All other exception classes are identical except for the name of the exception. Since it is tedious to type them repeatedly, the Xerces authors replace them with Macros:

C++
#define MakeDerivedException(DerivedException) \
class DerivedException : public BaseException \
{ \
public: \
 \
   DerivedException() : BaseException(){ } \
 \
   explicit DerivedException( const char* what) : \
       BaseException(what){ } \
 \
   explicit  DerivedException(const std::string &what) : \
       BaseException(what) { } \
 \
    virtual ~DerivedException() {}; \
 \
    DerivedException(const DerivedException& e) : \
       BaseException(e) { } \
  \
    DerivedException& operator=(const DerivedException& e) \
    { \
      BaseException::operator=(e); \
      return *this; \
    } \
};

Using Macros it is very easy to define any client exception,for example:

C++
MakeDerivedException (XSerializationException)   

Let's apply TFP idiom.
The base class doesn't change. The Macros will be replaced with class template:

C++
template <class>
class DerivedException : public BaseException
{
public:
   DerivedException() : BaseException(){ }

   explicit DerivedException( const char* what) :
       BaseException(what){ }

   explicit  DerivedException(const std::string &what) :
       BaseException(what) { }

    virtual ~DerivedException() {};
 
    DerivedException(const DerivedException& e) :
       BaseException(e) { }
  
    DerivedException& operator=(const DerivedException& e)
    { 
      BaseException::operator=(e);
      return *this;
    } 
};

We omit template parameter since it doesn't contribute to definition of DerivedException.
Now you can define a client Exception by a specialization of DerivedException :

C++
struct XSerializationException_Tag{}

typedef DerivedException< XSerializationException_Tag> XSerializationException;

Sample code is provided for both cases (look on demo-projects MacroException and FakeTempleteException).

Let's try to figure out which is a better solution. From the point of usage they are equivalent. Both of them allow us to avoid redundancy.TFP idiom allows your clients to inherit from the XSerializationException class to fit their needs. You can also derive from Macros definition.

We can look at the "Pro" and "Contra" sides of TFP idiom solution, versus the Macros solution.

Contra:

  • With Macro you use only one statement to define the derived class, while with TFP idiom you reach the same with two statements.

Pro:

  • "The first rule about macros is: Don't use them unless you have to." [7,§7.8]. You can consult C++ books [3,§2], [8,§16] for why it is generally better to avoid macros.
  • From practical point of view, TFP idiom allows you to debug your code with the compiler, while the macro solution does not.

Applying with Clone Classes

Clone classes arise in class hierarchies with a member function that creates an exact copy of derived object. It sometimes called "virtual constructor" in C++ [4], or, more generally, a Prototype pattern [9].
Consider the following prototype diagram:


Screenshot - faketemplate1.gif

The base abstract class IDataReader represents a prototype that declares an interface for cloning itself. Child classes implement an operation for cloning themselves, and the client creates DerivedReaders by asking prototype to clone itself.

Let's assume our Readers obtain some data from different sources. The sources can be files with different formats or data from a database. Thus, a specific reader has read a function that fills out some container, for example, a vector of strings.

Depending on particular data, Reader can return true or false, for example, if we check the uniqueness of some record in a table. So, we select data, and if we have more then one record with same ID, the reader should return false. Or, we need to retrieve a particular record from a table by some id. In the case that this record actually exists, we return true, and if not, we return false.

Generally speaking, our readers are very similar except for the implementation of the read function. Let's go back and provide some implementation for the classes.

Following the example in [8,§54], consider the sample code for the base class:

C++
class IDataReader
{
public:
    typedef std::auto_ptr<idatareader /> IDataReaderPtr;
    typedef std::vector<:string>   VecString;  
    
    IDataReader();
    virtual ~IDataReader() = 0;    

IDataReaderPtr clone() const
{
  doClone();
  IDataReaderPtr p = doClone();
  assert(typeid(*p)==typeid(*this)&&"doClone incorrectly overriden");
  return p;
}    
        
bool read(VecString& outs) 
{   
      return doRead(outs);
}   

protected:
        IDataReader(const IDataReader& reader);
        virtual IDataReaderPtr doClone() const = 0;
        virtual bool doRead( VecString& outs) = 0;
    private: 
        IDataReader& operator=(const IDataReader&);          

};

Let's look on some details of the implementation.

First, non-virtual interface (NVI) idiom [3,8] is used for the design. The base class defines the interface by non-virtual functions clone() and read() that call protected virtual function counterparts doClone() and doRead(). One of the advantages of using NVI is it allows us to include type checking in the clone() function. It will remind us if a further derived class doesn't implement the doClone() function or if the function doesn't return the object of the IDataReader type.

Next, instead of returning a pointer from the clone() function, we return auto_ptr (follow "source" idiom [10,§37]). The advantage is that it is a completely safe way to let the caller know about the ownership of the pointer. Even if the caller ignores the return value, the allocated object will always be safely deleted.

Now, let's start to implement our Derived classes. We need to provide for any Derived class constructor, destructor, copy of constructor, doClone() and doRead() functions, and explicitly disallow the assignment operator. For example, for our first Derived class:

C++
class DerivedReader1 : public IDataReader
{
  typedef IDataReader::IDataReaderPtr IDataReaderPtr;
public:
  DerivedReader1() : IDataReader() {}
  virtual ~DerivedReader1() {}  

protected:
    DerivedReader1(const DerivedReader1& rhs) : 
      IDataReader(rhs) { }

    
    virtual IDataReaderPtr doClone() const 
    {
      IDataReaderPtr ptr(new DerivedReader(*this));
      return ptr;
    }

    virtual bool doRead( VecString& outs);

private:
   DerivedReader&  operator=(const DerivedReader&);
};

As soon as we start to write our next derived classes we will see that we repeat the same code again and again. The code of one derived class differs from another only by the doRead() function. The other code is redundant.
So, following TFP idiom, we override the code:

C++
template <class>
class DerivedReader : public IDataReader
{
    // exactly the same code as in a class Derived1
};

create the tags :

C++
struct DerivedReader1_Tag{};
struct DerivedReader2_Tag{};

and specialize classes:

C++
typedef DerivedReader<derivedreader1_tag /> DerivedReader1;
typedef DerivedReader<derivedreader2_tag /> DerivedReader2;

Thus, instead of duplicating, we can work on implementation of doRead() functions for derived classes. For example,

C++
template <>
bool  Derived1Reader::doRead( VecString& outs)
{
    //read from file of one format
     
    std::cout << "Call Derived1Reader::doRead" << std::endl;
    outs.push_back("dummy_data1");    
  //return error if doesn't get some data
  return !outs.empty();
}

template <>
bool  Derived2Reader::doRead( VecString& outs)
{
  //read from file of another format
   std::cout << "Call Derived2Reader::doRead" << std::endl;
  outs.push_back("dummy_data2");
  //return error if get some data    
  return outs.empty();
}

In our code sample, we create upfront reader objects by "Cloning Factory" that map object with its name. The client calls the Factory create function and obtains a derived class by name. For more advanced Factory examples, see [1,§8].
Finally, here the prototype diagram in the TFP case:

Screenshot - faketemplate2.gif

Final Remarks

The TFP idiom can be useful in combination with several patterns: Prototype, Command,Template Method and etc[9] when your derived classes differ by behavior.
The main limitation in technique is that you can not introduce additional members of derived classes without full template specialization.

The "fake" or empty template parameters were used for specifying policies with template template parameters by Alexandrescu[1]. The idea of the empty tag comes from STL library, where the tag convention is used for design iterators [7].

As far as I know, the TFP idiom technique is as of yet unpublished.
I hope that this technique can be useful in your projects.

Acknowledgments

I would like to thank Philip Eskelin for discussion of the matter of the article . Thanks to my son Tim Kunisky for helping to prepare the article.

History

23 August 2007

Remove the statement(Thanks to my coworker Alex Urben who find the mistake.)
:

  • With the Macro definition, any Derived class is final. For example, we would not be able to derive from the macro implementation of the XSerializationException class.

References

  • [1] Andrei Alexandrescu, Modern C++ Design: Generic Programming and Design Patterns Applied. Addison-Wesley, 2001
  • [2] David Vandevoorde, Nicolai M. Josuttis, C++ Templates: The Complete Guide. Addison-Wesley, 2002
  • [3] Scott Meyers, Effective C++ (3rd edition). Addison-Wesley, 2005
  • [4] Stephen C. Dewhurst, C++ Common knowledge. Addison-Wesley, 2005
  • [5] International Standard for C++, ISO/IEC, 1998
  • [6] Xerces-C++ parser, http://xml.apache.org/xerces-c/
  • [7] Bjarne Stroudstrup, The C++ Programming Language (3r d edition), Addison-Wesley, 1998
  • [8] Herb Sutter, Andrei Alexandrescu, C++ Coding Standards, Addison-Wesley, 2005
  • [9] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Design Patterns, Addison-Wesley, 1995.
  • [10] Herb Sutter, Exceptional C++, Addison-Wesley, 2002

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralStrategy pattern analogy Pin
Lyfar Dmitriy30-Aug-07 0:19
Lyfar Dmitriy30-Aug-07 0:19 
GeneralRe: Strategy pattern analogy Pin
Alexei Valyaev1-Sep-07 8:02
Alexei Valyaev1-Sep-07 8:02 
GeneralATL approach Pin
Nicola Tufarelli24-Aug-07 2:41
Nicola Tufarelli24-Aug-07 2:41 
GeneralRe: ATL approach Pin
Alexei Valyaev26-Aug-07 17:25
Alexei Valyaev26-Aug-07 17:25 
GeneralRe: ATL approach [modified] Pin
Nicola Tufarelli27-Aug-07 11:04
Nicola Tufarelli27-Aug-07 11:04 
GeneralRe: ATL approach Pin
wtwhite27-Aug-07 18:23
wtwhite27-Aug-07 18:23 
GeneralRe: ATL approach Pin
Alexei Valyaev29-Aug-07 11:06
Alexei Valyaev29-Aug-07 11:06 
GeneralRe: ATL approach Pin
wtwhite29-Aug-07 17:23
wtwhite29-Aug-07 17:23 
GeneralRe: ATL approach [modified] Pin
Alexei Valyaev29-Aug-07 11:01
Alexei Valyaev29-Aug-07 11:01 
Generaldestructor syntax Pin
Jim Crafton22-Aug-07 8:33
Jim Crafton22-Aug-07 8:33 
GeneralRe: destructor syntax Pin
Alexei Valyaev22-Aug-07 13:29
Alexei Valyaev22-Aug-07 13:29 
QuestionIsn't this what some template classes already do? Pin
Shawn Poulson22-Aug-07 2:04
Shawn Poulson22-Aug-07 2:04 
AnswerRe: Isn't this what some template classes already do? Pin
Alexei Valyaev22-Aug-07 7:12
Alexei Valyaev22-Aug-07 7:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.