Polymorphism in C++

The term polymorphism is central to most discussions in and around object oriented design and programming. However I find that many people are still confused or don’t have a complete understanding of the advantages and disadvantages of using polymorphism.

I have heard many different simplified definitions of the root term for polymorphism, usually relating to chemistry or biology. Rather than trying to justify the name, I’ll give you my very simplistic definition from a software perspective.  Simply put polymorphism means:

Multiple functions with the same name.

Yep, as simple as that.

Most C programmers don’t realise they have been using polymorphic operations since they started programming. Take, for example, the following code:

b + c

That’s a polymorphic expression. Why?  Well we know nothing about the types of b and c. If b and c are of type int then the code generated is significantly different to if they are double.

But, I hear to shout, what about virtual functions and all that?

So herein lies on of the main problems. When most people use the term polymorphism they are actually referring to Dynamic Polymorphism. The expression b + c is related to Static Polymorphism.

With static polymorphism, the actual code to run (or the function to call) is known at compile time. C++ Overloading is static polymorphic, e.g.

void swap(int* a, int* b);
void swap(double* a, double *b);
int main()
{
   int x = 10, y = 20;
   double a = 1.2, b = 3.4;
   swap(&x, &y);            // swap(int,int)
   swap(&a, &b);            // swap(double, double)
}

Here the compile, based on the number and type of arguments, can determine which function to call.

Dynamic polymorphism, which in C++ is called Overriding, allows us to determine the actual function method to be executed at run-time rather than compile time.

For example, if we are using the  uC/OS-II RTOS and have developed a Mutex class, e.g.

class uCMutex
{
public:
   uCMutex();
   void lock();
   void unlock();
private:
   OS_EVENT* hSem;
   INT8U err;
   // not implemented
   uCMutex( const uCMutex& copyMe );
   uCMutex& operator=( const uCMutex& rhs );
};

And have also implemented  a very simple stack class (note this code is just for explanation purposes and has many shortcomings) that requires mutual exclusion, it may look something along the flowing lines:

class myStack
{
public:
   myStack();
   bool push(int val);
   int pop();
private:
   static const int sz = 10;
   int m_stack[sz];
   unsigned int count;
   uCMutex tm;   // uCMutex Object
};
myStack::myStack(iMutex& m):count(0), tm(m)
{
   memset(m_stack,0,sizeof(m_stack));
}
bool myStack::push(int val)
{
   bool retval = false;
   tm.lock();    // LOCK
   if (count < sz) {
      m_stack[count++] = val;
      retval = true
   }
   tm.unlock();     // UNLOCK
   return retval;
}
int myStack::pop()
{
   int val = -1;
   tm.lock();       // LOCK
   if (count != 0) {
      val = m_stack[--count];
   }
   tm.unlock();     // UNLOCK
   return val;
}

If, then, in our new design we are going to use VxWorks rather than uC/OS-II, our stack class would require reworking, thus:

class VxMutex
{
public:
   VxMutex();
   void lock();
   void unlock();
private:
   …
};
class myStack
{
private:
   ...
   VxMutex tm;
};

Even though the change from the uC/OS-II mutex to the VxWorks mutex class is within the private part of the stack class, this still has many detrimental knock on effects. Significantly, we have changed the stack class’s definition, so all files that use the stack now need recompiling. This, then, has a knock on effect to the amount of regression testing that is required.

An alternative strategy is to use dynamic polymorphism and interfaces to make our code more testable and reusable.  So, by defining an interface class for the mutex abstraction:

class iMutex
{
public:
   iMutex(){}
   virtual ~iMutex(){}
   virtual void lock() = 0;      // pure virtual function
   virtual void unlock() = 0;    // pure virtual function
private:
   // not implemented
   iMutex( const iMutex&);
   iMutex& operator=( const iMutex&);
};

We can alter the stack code so the mutex object is passed in as a constructor parameter (also the mutex classes require changes to inherit from the iMutex interface):

class uCMutex : public iMutex
{
}
class VxMutex : public iMutex
{
}
class myStack
{
public:
   explicit myStack(iMutex& m);
   bool push(int val);
   int pop();
private:
   static const int sz = 10;
   int m_stack[sz];
   unsigned int count;
   iMutex& tm;   // Mutex Reference
};

Our main code now becomes:

uCMutex ucm;
myStack ms(ucm);

or

VxMutex vxm;
myStack ms(vxm);

This is dynamic polymorphism in operation. Depending on the actual object passed (vxm or ucm), the actual code called when

tm.lock();

is executed, will either be VxMutex::lock() or uCMutex::lock().

Dynamic polymorphism is an incredibly powerful construct and, used well, creates code that can easily be adapted in the face of changing requirements with minimal impact.

However it all comes at a cost. The run-time lookup for virtual functions requires additional code and data. Each dynamic polymorphic class requires a virtual table (v-table), and each object of that type a v-table pointer (vtptr). To call the polymorphic function the run-time system requires indexing into the v-table via the object’s vtptr to actually call the function. In certain environments this can be twice as slow as a normal function call.

So how can we get the benefits of dynamic polymorphism, allowing us to abstract the code from how we’re doing it (e.g. VxWork’s lock call) to what we’re doing (Mutex lock call), but not have to extra overhead of virtual functions.

Well we have C++ templates. So modifying the stack class to become:

template <typename mutex_t>
class myStack
{
public:
   myStack();
   bool push(int val);
   int pop();
private:
   static const int sz = 10;
   int m_stack[sz];
   unsigned int count;
   mutex_t tm;   // Mutex Template Instance
};

and main becomes

 myStack<uCMutex> ms;
 ms.push(10);

With template based code we revert back to static polymorphism from dynamic polymorphism, as the actual call to tm.lock() will be compile time resolved at the possible expense of code readability and complexity.

Finally, I have found that the terms for polymorphism have a number of different names, e.g.

Dynamic Polymorphism

  • Subtype polymorphism
  • Overriding
  • Late binding

Static Polymorphism

  • Parametric polymorphism
  • Overloading
  • Early binding
Niall Cooling
Dislike (1)
Website | + posts

Co-Founder and Director of Feabhas since 1995.
Niall has been designing and programming embedded systems for over 30 years. He has worked in different sectors, including aerospace, telecomms, government and banking.
His current interest lie in IoT Security and Agile for Embedded Systems.

About Niall Cooling

Co-Founder and Director of Feabhas since 1995. Niall has been designing and programming embedded systems for over 30 years. He has worked in different sectors, including aerospace, telecomms, government and banking. His current interest lie in IoT Security and Agile for Embedded Systems.
This entry was posted in C/C++ Programming and tagged , , , , . Bookmark the permalink.

9 Responses to Polymorphism in C++

  1. Another informative article, Niall. Thank you!

    The more I use C , the more I like templates. This is the first time, though, that I have seen them touted so explicitly as an alternative, in some cases, to dynamic polymorhism. What an escellent idea!

    Your choice of example, although perfectly adequate for its purpose in elucidating the points made in your article, prompts me to further comment. This is off topic as I am making an entirely new and unrelated point. I hope you will excuse me...

    If I were making some kind of library, the details of which depended on the chosen operating system (or some other software) I would go to great lengths to avoid exposing the application programmer to these choices at all. In your example, the programmer has to know which type of mutex to use, either as a constructor argument or as a template argument. I would remove this complication from the programmer's day-to-day work by encapsulating the required options in a configuration file. Doubtless, this would contain the very kind of templates you describe - and possibly some polymorphism too - but it would also have conditional compilation and some old-fashioned macros in it to make the configurer's job as simple as possible. The idea would be to present the simplest possible classes to the application programmer, no matter how tortuous the private machinations of configuration might be.

    As I said, off topic, but possibly a seed for another posting. Your blog or mine?

    Like (0)
    Dislike (0)
  2. Pete says:

    Templates also have several costs associated with them: compile time, hard-to-read error messages, etc.

    In the case described here, where only one type will ever be used with the stack class, it seems pointless to use templates. Why not simply use a typedef instead? Somewhere in your code you specify which OS is used (probably with the preprocessor or equivalent -- your example would need this anyway) and stick a typedef in for the mutex class of that OS.

    I could maybe see a point in this approach if there were multiple mutex classes on each platform (perhaps a separate class for recursive mutexes, etc).

    As it is, with the method presented here you have to specify the OS everywhere that you create a mutex object... and use templates everywhere you use a mutex.

    Like (0)
    Dislike (0)
  3. Niall says:

    Hi Pete,
    Yes you're absolutely correct that templates have costs associated with them (especially those darn error messages).
    Typedef's in this context work perfectly fine. My major comment would be that they then add an extra level of coupling that stops the stack class (in this example) being a self-contained unit. Any changes to the mutex typedef has a knock on effect to the stack class, whereas the template option the impact is on the code defining the stack class object. for regression testing and code stability this maybe seen as detrimental.
    Like most things in life what you gain one hand you loose on the other.
    Thanks for the comment.
    Niall.

    Like (1)
    Dislike (0)
  4. Niall says:

    Note Peter's post should read C++ rather than C

    Like (0)
    Dislike (0)
  5. Pingback: Sticky Bits » Blog Archive » Inheritance, ABCs and Polymorphism

  6. mrn says:

    hi..
    sorry but i dint get the idea behind making iMutex class lock() and unlock() pure virtual. Can't we make a simpple iMutex class with no virtual lock() or unlock() and redefine these functions in derived classes. Then the extra overhead of vtable lookup would vanish. And the linking would be at compilation time. Please help me understand this pure virtual concept used here.....

    Like (0)
    Dislike (1)
  7. Niall says:

    Hi mrn,
    If you're refereeing to using template based polymorphism, you are correct, the (pure) virtual is redundant and, as you said, removing this would eliminate the v-table and v-table pointer. In reality, the need for the the interface class is redundant, as long as the class type instantiated by the Stack template supports a lock() and unlock() function it will build and run correctly.
    In hindsight, I should have added this to the end of the post.
    Thanks for your comment.
    Niall.

    Like (0)
    Dislike (0)
  8. Pingback: Typemock Isolator++: A Run-time Code Modification Method to Unit Testing – Coding Adventures

  9. aabdelfattah says:

    Thanks you for this article. Just what I needed to resolve the confusion between overloading and overriding.

    Like (0)
    Dislike (0)

Leave a Reply