关于C++模板封装的问题(关于C++模板参数的声明)
C++模板参数的声明很有意思,除了基本类型,还有可选的就是class和typename,当然了,到C++泛型编程的高级话题的时候,就还可以加上什么的模板模板参数(没有记错的化,侯捷就是这么翻译的)。
很久以前就觉得C++中模板声明在语法上有点问题,不过只是一种感觉,但是具体问题在哪又说不上来。这几天看《计算机程序的构造和解释》和《Design by Contract原则与实践》,不知道是因为和同学讨论,还是自己灵感来了,估计两者都有,终于发现了这个不优美语法的具体形式。呵呵,实在难以想象,全世界这么多的大设计师,其中不乏优秀的程序语言设计师,居然一直就是用着这种蹩脚而不多大价值的声明。
看过C++模板编程或者写过的人应该都知道,C++模板编程中经常会在其中对参数声明中的class或者typename定义对象,然后使用这些对象的方法。问题就出在这里!
使用这些对象的方法是没有显示说明的。也就是说:如果我们不能看到这个模板类或者模板函数的源代码,我们就不知道在将这个模板类或者模板函数具体化的时候,我们传入的类型参数(即使class和typename后面的参数)需要满足什么样的条件。如果想知道需要满足什么条件,那么就得看源码或者文档。这两种方法明显都是不提倡的。
其实,C++中经常把类和接口一起说,因为在C++中类和接口之间的界限本身并不明晰。当然完全理解后是很简单的概念,但是理解的过程,就我个人的经验,是一个相对漫长的过程。所以,人们交流C++的时候,经常忽视了这个问题,认为C++中有确实的接口。我在这个问题上讨论这么多,是因为后面我提出的解决方法的名称也叫接口,不过这个接口的意义才是真正意义上的接口,基本上来说就是和Java中的接口一个意义。
C++中,对模板参数需要满足什么条件是没有相应的规则的,也即使说语法上没有显示的做好这件事情。你要想写好模板类或者模板函数,就在很多时候都要假设模板参数有什么方法可以使用。当然了,当这个方法确定后,实际上就是说以后不管什么类名在这里作参数,这个类都需要有这个方法。理解这里复杂描述的特性后,就应该知道了其实这就是一种接口,只是这个接口定义用的是一种隐式的方式。所以我们编写泛型程序非常难也不足为奇了,因为我们不知道我们该怎么去写传给模板的参数。
问题我们知道了,那么如何解决呢?
解决的方法如此简单,真是让我自己都大跌眼镜。
1、 引入Java的关键字interface,表示纯粹的一个接口定义。写一个接口的时候,只需要所有的签名就可以了,也就是说声明是完整的就可以了。不需要实现,当然想提供一个默认的实现也是可以的。
2、 当编写模板类或者模板函数的时候,class或typename或其它的都用这个定义的接口名代替,这样就显示的表示了模板类或者模板函数对模板参数的要求。即这个传递进来的类型需要实现了这个被显示说明的接口。
3、 在声明传递给模板的参数对应的类的时候,我们在类名后学Java写上一个implement,然后加上前面声明的接口,实现中保证这个接口中的所有函数都被类所实现。
结合这3点,我们就知道了这样两件事情:1 模板类或者模板函数的模板参数必须满足什么样的要求(可以指导我们如何写对应的类或别的);2 自己定义的类满足了什么要求。可以看到这两个信息都一般在头文件中,是可以很容易的知道的。
解决的机制很完美。其实这个方法不能算是我想的,因为Java中类实现接口就是这样做的。可以这样类比,说两者同源,但是鉴于语言不同,很多要求不同,所以本质上还是有区别的。当然这还只是一个粗略的想法,没有具体深入的证明是否和目前C++的所有其它机制协调。
下面用一个简单的例子说明我的想法
:
尽管我这个例子不完整,但是说明问题是足够的。虽然很多其他模板参数类型的问题没有具体在其中提到,但是这个例子的目的只是是说明想法,所以只考虑核心问题。
标准C++的写法: | 我认为应该的写法: |
| interface tempRequire { void run(); }; |
class Foo { public: void run(){} }; | class Foo: implement tempRequire { public: void run(){} }; |
template<class T> class temp { public: void go(){_mem.run()} private: T _mem; }; | template<tempRequire T> class temp { public: void go(){_mem.run()} private: T _mem; }; |
by:kangtian0
e-mail:dunnashan@163.com
QQ:22724092