Implement Interface Mechanism Using Templates
Sorry the format is poor, I will update it later. Or please see the same article on my blog at baidu which has a better format: http://hi.baidu.com/dazhao_dbblog/blog/item/a1f49efcee4fba1e09244d06.html
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 and so on – 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++ by 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 Chinese, 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 (by 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 are provided, we can fall back to use 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.
最近评论