编程王 - King Of Coder

来源:互联网 发布:解压软件mac版 编辑:程序博客网 时间:2024/05/16 06:30

C++的泛型编程和限制参数类型的技术探讨

C++的泛型编程和限制参数类型的技术探讨



模板概述
泛型是C++中的重要特性。据说,已经在C++社区中已经取代面向对象成为C++的主要编程泛型。STL和boost库等都广泛使用了泛型。
泛型,就是C++的模板机制。
模板可以看作是C++宏的衍生。宏,就相当于是文本文件中的替换。C++编译器在编译前,先把所有使用宏的地方,用宏的定义替换掉宏。
在Java,.net,ruby等现代语言中都没有宏这种语法的地位。
宏是另程序变得晦涩难懂的一个原因!我认为在程序中应该尽量避免使用宏!

模板也可以看作是一种模板。C++编译器在编译之前,将创建模板的具体类型的源代码,然后再编译成二进制代码。



模板技术

模板类的声明和定义,形如:
template class Manage{…全部内联函数实现!};


函数模版的定义,形如:
template void trim(SequenceT &, const std::locale & = std::locale());
模板的特化
模板类的特化
1)首先定义基泛型:
template class Manage{…全部内联函数实现!};

2)然后定义特化的泛型:
#include 上面基泛型的文件
template<> class Manage
{…全部内联函数实现!};

特化的泛型必须自己实现所有基泛型定义的成员函数和静态成员。

模板类的成员函数的特化
如果我们希望特化的泛型继承绝大部分的基泛型的代码。
那么只需定义特化的成员函数即可!
在基泛型的定义后面加上特化成员函数:
template<>
void Manage::sayHello(void){
cout<<"B"< };
这个特化的函数就是特化模板类的成员函数。
实际上,这相当于是隐式定义了上面的那样一个特化模板类,并且所有的基本实现使用基泛型模板的实现!

偏特化/部分特化
就是一个模板类有多个泛型参数。
我们特化一个模板参数:
1)基泛型有多个模板参数:
#pragma once
#include "cppunit/extensions/HelperMacros.h"
#include "B.h"
#include using namespace std;

template class Manage
{
private:
T* t;
public:
Manage(void){
this->t=new T();
};
void sayHello(void){
cout<<"管理"< };



public:
virtual ~Manage(void){};
};

2)定义的特化有一个还是任意的类型参数
#pragma once
#include "Manage.h"

#include "B.h"
#include /*相当于
template class Manage
没有对应已有的类型B
*/
template class Manage
{

private:
B* t;


public:
Manage(void){
this->t=new B();
}
virtual ~Manage(void){};

public:

void sayHello(void){
std::cout<<"B类"<t<t< };


};

但是,请注意,半特化,则没有特化中对应的成员函数的特化那种简单扼要的形式!!!

模板的使用
使用模板的类应该写在头文件中,并以源码的方式发布
C++的泛型编程中,需要把所有使用到泛型声明或者定义的代码都直接写在.h头文件中,不能写在.cpp文件中,否则会有很多奇怪的错误!

VC2005也还没有支持分离编译的export关键字!

模板类只能写在一个.h文件中。而且,不可以放在dll项目中。因为模板类是无法导出的!
导出以后的模板类,只能够在外部声明这个模板类,不能够实际创建模板类的对象!否则会报告
TestMain.obj : error LNK2019: 无 法解析的外部符号"__declspec(dllimport) public: __thiscall net_sf_interfacecpp_core_lang::ObjectRefManage(void)? AClass>::ObjectRefManage这样的错误。
因为,模板类实际上并没能编译成二进制代码。它只是一个宏!需要在编译时根据客户代码的使用情况生成源代码,然后再变成二进制代码。
因此,作为宏,它应该在.h文件中。作为源代码的元数据,应该共享给用户。因为它需要根据客户的使用情况来生成源代码。因此,它必须在最终客户代码一起!

要使用模板类,就必须把它单独拿出来,把.h这个头文件/源代码交给用户。
用户在项目中直接作为源代码使用这个头文件,才能够使用这个模板类!



//确保只被引入系统一次
#ifndef _net_sf_interfacecpp_core_lang_ObjectRefManage_h_
#pragma once
#include ".. et_sf_interfacecppIObject.h"
//下面是自定义的所有.cpp文件都需要引入的头文件
//#include "ConfigApp.h"
#include ".. et_sf_interfacecppObject.h"

#pragma comment(lib,"../debug/net_sf_interfacecpp.lib")
/*
用于管理任意类的实例的生命周期,使之符合IObject接口
模板类必须定义在头文件中
NET_SF_INTERFACECPP_API
*/

namespace net_sf_interfacecpp_core_lang{
template
class ObjectRefManage:public IObject
{
private:
IObject* pIObject;
T* pT;
//copy构造函数
ObjectRefManage(const ObjectRefManage &that);
//重载等于操作符
ObjectRefManage& operator=(const ObjectRefManage &that);
//void operator delete(ObjectRefManage* thisPtr);
public:
T* getObjectPtrAndAddRef(){
this->addRef();
return this->pT;
};
T* getObjectPtrNotAddRef(){

return this->pT;
};
ObjectRefManage(void){
//现在引用是
this->pIObject=new Object();
this->pT=new T();
};

long addRef(){
return this->pIObject->addRef();
};
long release(){
long result=this->pIObject->release();
if(result==0){
delete this->pT;
delete this;
return 0;
}
};
void setSingleton(){
this->pIObject->setSingleton();
};
public:
virtual ~ObjectRefManage(void){};
};
}
//确保只被引入系统一次
#define _net_sf_interfacecpp_core_lang_ObjectRefManage_h_
#endif

dll依赖模板时使用方式
1)模板依赖于我们的dll
2)如果我们的类需要使用这个模板,就需要另外建一个dll—ext.dll,包括这个模板,从而间接包括核心dll。
Dll内部时可以使用模板的,因为可以直接在生成dll时根据内部的使用模板的情况,创建源代码,编译成dll。
但是,如果把dll内部的模板发布出去,这就不行了!
3)这个模板头文件和dll必须同时提供,避免找不到模板依赖的dll而出错!



对模板参数没有限制是一大误区
考察STL和boost中使用泛型的例子。我发现一个问题。使用模板的类,在使用时,程序员可以指定任何类和基本类型。
但是,实际上,很多模板类在代码的内部实现中,对参数类型能够提供的操作实际上是有要求的。如,需要>,<,=等操作是有意义的。
或者需要能够调用某个方法。
但是,STL和boost的库中,均没有对参数进行限制!
这样,如果客户程序员使用了错误的参数类型,那么程序还是能够正常编译。只有在运行到这段代码时,才会报错。
甚至,由于STL和boost喜欢使用操作符重载,因此,即使运行时,也不会出错,只是真正的逻辑错了。这样的问题,怎么才能找到错误点呢?我不禁倒吸了一口凉气!

翻开C++之父BS的《C++语言的设计与演化》一书,BS本人对模板的这一描述,令我乍舌!
BS居然认为不需要限制模板的参数类型。认为对模板参数的限制是OOP程序员的偏见!
晕!C++是静态编译型语言,不是ruby,python,JavaScript这样的动态面向对象语言。
如果ruby开发中,你用了错误类型的对象,执行时没有报错,直到你运行到这段代码才报错,那我也没什么话好说的。人家是解释型语言,放弃了编译检查错误,但换来了语言的巨大动态灵活性。有所得必有所失嘛!这我就不说它了!
但BS认为C++不应该限制模板的参数类型,听任错误在运行时爆发,就让我无法理解了!

BS,不能因为你对模板的偏爱,让这么多C++程序陷入危险啊!

通过派生对模板的参数类型加以限制的一种方法。
形如:
Template >Template class Vector{};
BS认为不应该采用这种方式。
在java中使用模板时,我们经常使用这种方式。
如:
Public MyClass{……}

但,BS认为这种方式不好。而且我在VS2005中也无法编译这样的代码。
确实,这样会让模板类的数量直线上升。

第二种BS提到的方法非常丑陋。
就是让每一个方法的实现都转换成我们需要的类型。这样编译时就会报错。

第三种方法,就是使用模板的特化,或者叫做专门化。
这是BS推荐使用的方法。我也认为应该使用模板特化来限制模板的参数类型。
尽管BS提出这种语法的本意并不是用来限制模板的参数类型。
因为,BS根本就不认为应该限制模板的参数类型。偏执的家伙!
使用模板特化限制模板的参数类型
作为一个坚定的OO程序员,我是不会容许在自己的C++程序中像STL和boost那样,允许任意参数类型随意使用我的模板类的!
BS的观点,我不能苟同!

我认为,可以使用模板特化限制模板的参数类型。这种办法是最简单有效的。
首先,我们定义一个基范型。

然后再在基范型模板类的外部定义几个重载的方法。
指定如果是我们需要的参数类型,应该执行这些方法。

也可以独立定义特化的模板类。但是,我们上面已经说过了,特化模板类,不如特化模板类的成员函数合算!

最后,我们在基范型的实现中,抛出一个自定义的异常。这样,如果使用了错误的类型,就会抛出异常,导致系统停止运行。我们的客户就可以发现问题所在。

当然,编译时,即使是不正确的类型,还是能够编译通过。只有在运行时才会把错误抓出来。
编译时检查不出错误,这只能怪BS和C++标准委员会没有为我们提供限制模板的参数类型的语法了。


补充:C++的模板和java的模板的异同
Java5中,也引入的泛型语法。如:
Public MyClass{……}
看上去类似,但是实际实现却非常不同。
C++的模板,是会在编译时,先生成很多新的C++类。因此,C++中使用模板有一个问题,就是模板生成的源代码可能太多。引起编译的性能问题。

而java的模板实现机制完全不同。Java模板类在编译时,会“擦除”类型信息。
不会生成新的java类的源代码。
因为,java的类继承体系是单根的,所有类都是Object类的子类。因此,在Java5之前,没有引入模板这个语法之前,java和它的集合实现类也过得很滋润。
Java模板类在编译时,我猜想是这样子的:
1,首先,擦除模板类型的信息,还是使用原来的Object类型。
2,在所有使用模板的参数类型的地方,加上强制类型转换,转换成程序员指定的模板参数类型。

我特别记得BS的一句话,认为特有道理:
他在C++中特别把不应该使用的语法设计得丑陋,让你不想去使用。如:
dynamic_cast < type-id > ( expression )
动态类型转换。
BS认为,显式的类型转换通常是不必要的。应该避免。

我深深地赞同这句话。Java引入模板,应该就是为了这个原因。现在,写Java代码可以少用很多强制类型转换!
用错模板的参数类型,Java编译器都会准确地报告错误。

唉,C++的模板要是也这样就好了!


本文转自
http://kingofcoder.com/viewNews.php?type=newsCpp&id=129&number=2384167329927005718788906612824062579302205906477356156718001417764694914414152897402184085136802272
t<