Implement Interface Mechanism Using Templates

本文探讨了如何使用C++模板实现超越传统接口机制的功能。通过模板,可以在不强制继承的情况下实现接口,提供了更大的灵活性。文章还介绍了如何利用模板支持各种类型,包括原始类型,并提供了一个具体的例子来说明如何为不同类型定义统一的操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值