Implement Interface Mechanism Using Templates

C++ template is really a powerful gun in the entire C++ artillery, it is an important part of the defining
components that make C++ so powerful, yet so difficult to learn. --- There are many ways you can make mistakes
when using C++, or using C++ templates, just like using any other powerful man-made systems. But if you make
everything right, you can gain incredible power, flexibility, efficiency, performance, extendability, ...
anything you dream of in the software engineering world. So it is definitely worthwhile to make the effort to
learn C++ well.

In this article I'd like to talk about how to implement the superset of interface mechanism using templates in C++ ---
using C++ templates, you can do a lot better than ordinary interface mechanism.

In C++ we don't have an "interface" language component like Java, so we use pure abstract class to simulate it, and
which is enough for a Java style ordinary interface. But if we use templates, we can gain a superset of
interface functionalities, then you will find how limited and naive the Java interface is.

Ordinary interfaces restrict any type which implements the methods required by an interface A to derive from
the interface A, otherwise it is not suitable for the tasks declared to require A even if it has all the
required methods, i.e. ability. The interface A is a tag imposed to the two parties of the contract ---
the provider P has to have this tag A --- to be a descendant of A --- in order to work with the consumer T
which requires a provider with the abilities defined in A, otherwise even if P has the ability required, it
is not suitable and can't be used. This is caused by the limitation that T has to express the requirement of
abilities in the form of "You have to be a descendant of A".

In China there is a saying: "王侯將相,寧有種乎!", meaning "A king is not a king because he is born by a
King! (Anyone who can do the king's job can be a king!)" . As said above, P has to be a descendant of A
in order to work with T, which is just a vivid example of this unfareness. This unfairness makes it very
restricted and limited for software developers in many ways, let's see some of them:

1. When you can't derive P from the interface A
When P is a written class built into a shared module (.dll, .so files), or for other reasons you will probably meet
in any real world project, you just can't derive P from A,  and you can't derive a new class from P in this case either,
thus you can't use an instance of P to work with T.


But if you are using templates, as long as you define the set of methods required by T in P, and T is also
using templates to express the requirement, then you can always use an instance of P
to work with an instance of T.

The way T express the requirement "You must have the set of methods defined in A" is: don't say anything
but simply assume the requirement is satisfied. And if not, there will be compiling errors.

Because of this flexibility, you can define methods required by several classes in P, but not adding any interface
tags to P, and P will be suitable for all occasions where any subset of P's set of methods are required.

2. Primitive types --- technically unable to implement any interface
Primitive types like int, double, char*, etc, can not derive from any interface or class, what if you want an
interface to mediate the P and T? This is something I met several months ago.

My use case was: I wanted to do marshal and un-marshal of various types in order to store/retrieve them into/from
a database in a consistent way. For each type T, I need a function to return an object's size in bytes, a function
to marshal an object of T-- putting its bytes to store into a chunk of memory, and a function to unmarshal ---
filling an object's fields with a chunk of memory previously marshaled. The three types of functions all have
default behaviors ---  sizeof operator for size measuring, memcpy for marshal and unmarshal --- when no functions
provided, we can fall back to default behaviors.

The types include primitive types as well as class/struct types, so obviously I can't use ordinary interface. But I
can do so using templates. Details as follows:

1. Define a TypeTraits<T> class template, make it a singleton.

2. In TypeTraits<T>, the three types of functions can be defined as three types of function pointer:

typedef size_t (*size_func_t)(const T&)
typedef void (*marshal_func_t)(void *dest, const T& src);
typedef void (*unmarshal_func_t)(T&dest, const void*src);

3. Define data members of the three types as well as the get/set functions for the data members.

This way users can assign different functions for different types, or don't assign at all but use default behavior if
appropriate.

In places using this "INTERFACE", we first check if the needed function is registered, if not, use default behavior,
otherwise use the registered function.

This way, all types are well supported.

There are a lot of things we can do with templates very gracefully and easily, I will cover them in later articles.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章