Home » Algorithms » Designing an efficient and low resource messaging framework. Part 3

Designing an efficient and low resource messaging framework. Part 3

In the last post I showed how to shrink the vtable overhead and reduce the coupling of the framework.
This time I shall show how to reduce the vtable hit even further using another technique called the Curiously Recurring Template Pattern or CRTP. This oddly name pattern is a way of replacing runtime virtual calls with compile time dispatch. It achieves this by passing the derived class type as a template type parameter to the base class.

template <typename TDerived>
class Base
{
};

class Derived : public Base<Derived>
{
};

By the use of this technique, the base class can be informed of its concrete type at compile time. This allows the base type to directly call member functions in the derived type thereby avoiding an indirect call though the vtable. If the called functions are deemed suitable by the compiler, then they may well be inlined too.

template <typename TDerived>
class Base
{
  void FunctionBase()
  {
    TDerived& derived = static_cast<TDerived&>(*this);

    derived.FunctionDerived()
  }
};

class Derived : public Base<Derived>
{
  void FunctionDerived()
  {
  }
};

This is the technique that we will use for the third version of the messaging framework.
The messages are defined in the same way as before.

class IMessage
{
public:

  IMessage(int id)
    : messageId(id)
  {
  }

  int GetMessageId() const
  {
    return messageId;
  }

private:

  const int messageId;
};

template <int ID_>
class Message : public IMessage
{
public:

  Message()
    : IMessage(ID_)
  {
  }

  enum
  {
    ID = ID_
  };
};

// Message 1.
class Message1 : public Message<1>
{
};

// Message 2.
class Message2 : public Message<2>
{
};

// Message 3.
class Message3 : public Message<3>
{
};

// Message 4.
class Message4 : public Message<4>
{
};

As is the base message processor interface.

class IMessageProcessor
{
public:

  virtual void Receive(const IMessage& msg) = 0;
};

The difference now is in the definitions of the templated message handlers. The derived message processor type is passed as a template type parameter and used to cast the ‘this’ pointer to the true concrete type, allowing direct calls to the handler functions.

template <typename TDerived, typename T1, typename T2 = void, typename T3 = void, typename T4 = void>
class MessageProcessor : public IMessageProcessor
{
public:

  void Receive(const IMessage& msg)
  {
    TDerived& derived = static_cast<TDerived&>(*this);

    switch (msg.GetMessageId())
    {
      case T1::ID: derived.Handle(static_cast<const T1&>(msg)); break;
      case T2::ID: derived.Handle(static_cast<const T2&>(msg)); break;
      case T3::ID: derived.Handle(static_cast<const T3&>(msg)); break;
      case T4::ID: derived.Handle(static_cast<const T4&>(msg)); break;
      default: derived.Unhandled(msg); break;
    }
  }
};

template <typename TDerived, typename T1, typename T2, typename T3>
class MessageProcessor<TDerived, T1, T2, T3, void> : public IMessageProcessor
{
public:

  void Receive(const IMessage& msg)
  {
    TDerived& derived = static_cast<TDerived&>(*this);

    switch (msg.GetMessageId())
    {
    case T1::ID: derived.Handle(static_cast<const T1&>(msg)); break;
    case T2::ID: derived.Handle(static_cast<const T2&>(msg)); break;
    case T3::ID: derived.Handle(static_cast<const T3&>(msg)); break;
    default: derived.Unhandled(msg); break;
    }
  }
};

template <typename TDerived, typename T1, typename T2>
class MessageProcessor<TDerived, T1, T2, void, void> : public IMessageProcessor
{
public:

  void Receive(const IMessage& msg)
  {
    TDerived& derived = static_cast<TDerived&>(*this);

    switch (msg.GetMessageId())
    {
    case T1::ID: derived.Handle(static_cast<const T1&>(msg)); break;
    case T2::ID: derived.Handle(static_cast<const T2&>(msg)); break;
    default: derived.Unhandled(msg); break;
    }
  }
};

template <typename TDerived, typename T1>
class MessageProcessor<TDerived, T1, void, void, void> : public IMessageProcessor
{
public:

  void Receive(const IMessage& msg)
  {
    TDerived& derived = static_cast<TDerived&>(*this);

    switch (msg.GetMessageId())
    {
    case T1::ID: derived.Handle(static_cast<const T1&>(msg)); break;
    default: derived.Unhandled(msg); break;
    }
  }
};

The derived message processors are very similar to before, with the exception that the derived class is passed as template type parameter to the base.

class Processor1 : public MessageProcessor<Processor1, Message4, Message2, Message1, Message3>
{
public:

  void Handle(const Message1& msg)
  {
    std::cout << "Processor1 : Message1\n";
  }

  void Handle(const Message2& msg)
  {
    std::cout << "Processor1 : Message2\n";
  }

  void Handle(const Message3& msg)
  {
    std::cout << "Processor1 : Message3\n";
  }

  void Handle(const Message4& msg)
  {
    std::cout << "Processor1 : Message4\n";
  }

  void Unhandled(const IMessage& msg)
  {
    std::cout << "Processor1 : Unhandled IMessage\n";
  }
};

class Processor2 : public MessageProcessor<Processor2, Message3, Message1>
{
public:

  void Handle(const Message1& msg)
  {
    std::cout << "Processor2 : Message1\n";
  }

  void Handle(const Message3& msg)
  {
    std::cout << "Processor2 : Message3\n";
  }

  void Unhandled(const IMessage& msg)
  {
    std::cout << "Processor2 : Unhandled IMessage\n";
  }
};

We now have a message framework where the processor classes contain just one virtual function and where calls to the handler functions are direct.

The tests are defined exactly as in every previous version of the framework, as is the output.

int main()
{
  Processor1 p1;
  Processor2 p2;

  Message1 m1;
  Message2 m2;
  Message3 m3;
  Message4 m4;

  p1.Receive(m1); // "Processor1 : Message1"
  p1.Receive(m2); // "Processor1 : Message2"
  p1.Receive(m3); // "Processor1 : Message3"
  p1.Receive(m4); // "Processor1 : Message4"

  p2.Receive(m1); // "Processor2 : Message1"
  p2.Receive(m2); // "Processor2 : Unhandled IMessage"
  p2.Receive(m3); // "Processor2 : Message3"
  p2.Receive(m4); // "Processor2 : Unhandled IMessage"

  return 0;
}
Processor1 : Message1
Processor1 : Message2
Processor1 : Message3
Processor1 : Message4
Processor2 : Message1
Processor2 : Unhandled IMessage
Processor2 : Message3
Processor2 : Unhandled IMessage

Chain Of Responsibility

If the ability to set a successor message processor is added to IMessageProcessor, the framework is then able to implement the Chain Of Responsibility design pattern.


// IMessageProcessor base class
IMessageProcessor* pSuccessor;

void SetSuccessor(IMessageProcessor& successor)
{
  pSuccessor = &successor;
}

// Derived message processor class
template <typename TDerived, typename T1>
class MessageProcessor<TDerived, T1, void, void, void> : public IMessageProcessor
{
public:

  void Receive(const IMessage& msg)
  {
    TDerived& derived = static_cast<TDerived&>(*this);

    switch (msg.GetMessageId())
    {
    case T1::ID: derived.Handle(static_cast<const T1&>(msg)); break;
    default:
	{
	  if (pSuccessor)
	  {
	    pSuccessor->Receive(msg);
      }
	  else
	  {
	    derived.Unhandled(msg); break;
	  }
    }
  }
};

A version of this framework can be found as part of the Embedded Template Library, an open source C++ library distributed under the MIT licence.

Download the example code

John Wellbelove

John Wellbelove

Director of Aster Consulting Ltd
United Kingdom
I have been involved in technology and computer systems for all of my working life and have amassed considerable knowledge of designing and implementing systems that are both performant and correct. My role normally encompasses the entire project life-cycle, from specification to maintenance phase. Most systems I have worked on have required high speed and deterministic performance, often within a highly constrained platform. I am experienced in designing and adapting algorithms to solutions that are both space and time efficient, avoiding the normal overheads of standard solutions. Acting as a mentor for colleagues has often been a significant, though unofficial, part of my role.

I administer an open source project on Github.
See http://www.etlcpp.com/

Leave a comment

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