Design Patterns in C++

While having a discussion on Design Patterns there are group of implementers who advocate implementing design patterns using the so called best features of the Programming language. While I too agree, that implementation of patterns should certainly use features of underlying language it could be challenging to decide what defines the best? It seem to be specially true when it comes to programming with C++.

Here is my analysis on the subject based on one my recent discussion with a C++ implementer and his arguments.

Before moving to the debate let us for the shake of building the grounds focus on the basic motivations for Design Patterns. First we quickly  look at its what, why, when and how of design patterns.

What is Design Patterns?

A design pattern is a general reusable solution to a commonly occurring problem in software Design.

Well established. Right? So let us look at subtle aspects of design patterns:

Why and When Use Design Patterns?

  • Design patterns can speed up the development process by providing tested, proven development paradigms.
  • Effective software design requires considering issues that may not become visible until later in the implementation.
    • Reusing design patterns helps to prevent subtle issues that can cause major problems,
    • it also improves code readability for coders and architects who are familiar with the patterns.
  • In order to achieve flexibility, design patterns usually introduce additional levels of indirection
    • In some cases may complicate the resulting designs and hurt application performance.

 

How of Design Patterns

There are two major approach which is clearly emphasised in almost every design patterns:

  1. Prefer Interface Based component – This helps us achieve Polymorphism and results into dynamic reusable design.
  2. Prefer composition over Inheritance – This ensures a completely decoupled design.

 

So where we have reached. To sum of both the pros and the cons of using Design Patterns:

Design Patterns helps us architect dynamic, reusable, scalable, readable and manageable software. As a trade-off certain design tend to become complicated and hurt application performance.

 

Now let us have a look at the different ways to implement some of the design patterns in c++ and the debate over it.

Template Vs Polymorphism

In C++ many patterns, it appears can be implemented using templates. In fact you can carry the very same idea can be applied as generics in both .Net and Java programming scenarios. But then considering templates as an alternative of polymorphism is taking it a bit too farther.

Templates essentially is an alternative of function overloading; that too when the exactly same algorithm sequence of instruction needs to be applied on different data type. Period. Consider the classical example of a swap function below.

template <class T >
void swap(T &one, T & two)
{
    T temp=one;
    one=two;
    two=temp;
}

You can use the above template function to swap simple scalar types such as int and float but what about swap string or complex data (needing deep copy)?

Class templates are an extension of the same idea where a group of functions (carrying exactly same sequence of instruction) need to be applied on different data types. A good example will be collection classes such as Linked List or Stack. But the bottom line is they must always do the same thing exactly the same way know before hand.

We must realize the major limitation of C++ templates:

  1.  Templates allow you to choose different data type but not different algorithms
  2. Templates partially replaces overloading (static/pseudo polymorphism) and never overriding. They don’t replace interfaces (real polymorphism)
  3. Templates are design time tool. No runtime dynamic polymorphism can be expected.

Verdict:

Templates are effective constructs and reduces redundant (not intellectual) coding. However, since they are strictly compile time construct they can’t create dynamic runtime scalable applications. Moreover, if used as a replacement of Interface, you loose hierarchy. Above all you always run the risk of violating open-close principle

Functors Vs Interface

 

Functors or Function Objects are another construct in C++ that tend to replace interface in delegate patterns such as strategy or command. They are often considered as high performant than interface based counterpart. I doubt the difference will even be measurable, however let us not debate. consider a simple implementation of strategy using interface below:

template<class Item, class Parameter>
class IComparer
{
public:
    virtual int Compare(Item item, Parameter parameter)=0;
};

class Array
{
public:
    template<class I, class P>
    static int FindFirst(I *items,int size, P item,IComparer<I,P> *match)
    {
        for(int i=0;i<size;i++)
        {
            if(match->Compare(items[i],item)==0)
                return i;
        }

        return -1;
    }

};

Now we can test the application using a client application below:

//Entity object

class Person
{
public:
    char * name;
    int age;
    Person(char * name,int age)
    {
        this->name=name;
        this->age=age;
    }
};

//Concrete Strategy

class AgeComparer :public IComparer<Person,int>
{
public:
    int Compare(Person p, int age)
    {
        return p.age-age;
    }
};

//Test Client

int main()
{
    Person persons[5]={ Person("Ketan", 33),
                        Person("Fagun",33),
                        Person("Aditi",4),
                        Person("Toshu",2),
                        Person("Subodh",50)
                        };

    AgeComparer c;
    int result=Array::FindFirst(persons,5,4,&c);
    if(result==-1)
        cout<<"not found\n";
    else
        cout<<endl<<persons[result].name<<"\t"<<persons[result].age;
    cout<<endl<<"Hello World";
    return 0;
}

This is pretty much the same way you will write strategy is other languages such as java and .Net too. But then  C++ programmer can choose to go by the function object path too. So Let us have a look at the function object option. We will re-write the code with function object instead of IComparer interface.

class Array
{
public:
    template<class T, class S,class C>
    static int FindFirst(T *items,int size, S item, C criteria)
    {
        for(int i=0;i<size;i++)
        {
            if(criteria(items[i],item)==0)
                return i;
        }

        return -1;
    }

};

You will find we have done away with the IComparer interface. Template parameter C criteria is a function object and is used in place of interface. Cool and Clean. No need of Interface and the application still works. Here is the client code

class Person
{
public:
    char * name;
    int age;
    Person(char * name,int age)
    {
        this->name=name;
        this->age=age;
    }
};

class AgeChecker
{
public:
    int operator()(Person p, int age)
    {
        return p.age-age;
    }
};

class AgeChecker2
{
public:
};

int main()
{
    Person persons[5]={ Person("Ketan", 33),
                        Person("Fagun",33),
                        Person("Aditi",4),
                        Person("Toshu",2),
                        Person("Subodh",50)
                        };

    AgeChecker c;
    int result=Array::FindFirst(persons,5,33,c);   
    if(result==-1)
        cout<<"not found\n";
    else
        cout<<endl<<persons[result].name<<"\t"<<persons[result].age;
    cout<<endl<<"Hello World";
    return 0;
}

Here the AgeCheker class doesn’t need to implement any interface. It just (still) need to define operator () to qualify as function object and still works. Isn’t it.

So What’s the problem with Function Object?

 

Well the API assumes that the last parameter in funciton Array::FindFirst() is going to be from a class that over loads (). However, the question is how will the API know it. The answer is simple compiler will verify if the object passed as parameter actually contains overloaded (). And this is the problem. It is once again a static design time solution. And this is just one of the problems:

  1. The function object is a design time solution. It is not dynamic. Once again it is seem to violate open-close principle.
  2. The object should be passed as value or reference but not as pointer (remember overloaded operator assumes object as parameter and not pointer). So you can’t work with dynamic object (new) easily.

Do you know this is how it is implemented in COM/ATL ?

Well yes function objects and templates are extensively used in COM/ATL. But then with C++ you didn’t had much of a choice. Absence of Reflection was one big issue C++ programmer constantly battled. And being honest if C++ had such an elegant syntax probably we never needed to lay down design principles and patterns.

It was the limitation of C++ language and features that led to search and documentation of better programming/design principles and practices.

More over it must be noted that most of the programming language choose to follow the interface based paradigm instead of C++ function objects. And it was a decision by choice. Also even Microsoft choose to move ahead with interface driven design in .Net instead of sticking to more than decade old COM/ATL paradigm.

How do you answer that even C# implemented Function object as Delegates?

This too seem to be as much as myth as the fact that delegates are function pointers. Let us be very clear delegates are classes and not just an overloaded operator. A delegate is very much promote interface based programming where delegate class can be considered as interface pointing to different methods from different classes. function objects just can’t match. Period.

But Surely you can’t Ignore Performance!!!

Three Points Here.

  1. Interface based programming passes object by pointer (4/8 bytes) and involves invoking a method with double indirection (consider VPTR). Function object requires passing object by value or reference (4 or many bytes). And it still requires a function call. Direct function call. So performance cant be much of a problem. Sure can’t be visible.
  2. Even if with little performance issue the benefit we get dynamic runtime  polymorphism, An Open-Close design. Which seem, you will agree, is a small price to pay.
  3. We already mentioned design patterns is about re-usable design and we have accepted the performance part when choosing to go by design patterns. Design pattern is about best Object Oriented Practices. Not following interface and dynamic polymorphism is actually betraying to true essence of Design Pattern.

 

Wait!!! Even Function Object and Templates are reusable…

Sure if you choose to ignore runtime issues and choose to ignore Open Close Principle. Yet there is another form of re-usablity that I am concerned. Re-usability doesn’t always mean code based reusability. Consider the migration issues. What if the code need to be migrated to .Net or Java. Programmer will have more challenging job to migrate a function object than they would require for interface based design. Moreover design patterns are meant to provide common platform to understand and discuss design and here functor seem to be failing…

Final Thoughts…

Function Objects and templates are interesting and useful features of C++ language. But they result in strictly static solution and tend to violate Open Close Principle. They have there place and usability but shouldn’t be used to replace Interface driven design which has far greater impact. In fact using template along with Interface seem to yield greater benefits.

Finally execution performance shouldn’t be an excuse for bad design. Better optimizing compilers and faster hardware take care of performance, however, there is just no replacement better programming approach.

 

The summary is my final thoughts. I would be keen is having your feedback on the same issue.

Leave a Reply

Your email address will not be published. Required fields are marked *