【良少的专栏】

余好读书,乐思善疑。有过目不忘之能,亦有掩卷即忘之病,故尝攥文以记之。

沈东良ID:shendl
241069次访问,排名240好友3人,关注者84
[加为好友] [即时聊天] [发私信]
shendl的文章
原创 106 篇
翻译 4 篇
转载 19 篇
评论 551 篇
良少shendl[Edward Shen]的公告
人生信条[我可以被打倒,但绝对不会被打败!]偶像[努尔哈赤] 原因[匹夫一怒可倾国]
最近评论
njzhiyuan:不完全同意楼主对C/C++的评价,我就只会C\C++\Java等几种计算机语言.我在用Java写数据库程序时,采用的是连接池技术,但我在封装数据库操作类时,遇到了个不小的麻烦,那就是释放连接,我可以在封装类中重写finalize()方法,在其中加入连接释放代码,以便类对象失效后,被垃圾回收器回收时调用释放,但这在时间上有很大的不确定性,我是希望对象失效后马上释放它所占资源,这可以调用Syst……
lym19833:我只想说现在最重要的语言是js。楼主说自己不喜欢页面开发,喜欢底层。那我只能说楼主根本就没有进行过页面的开发,页面时小道?在我看来除了操作系统以外的底层开发全是二层开发。也不见的是什么大道,哈哈。
lqefn:没有技术含量, 大量个人不成熟的观点.
shendl:#nnxxn 发表于2008-07-14 08:32:33 IP: 123.115.141.*
尽量不要使用操作符重载
为什么?
==================
因为重载操作符虽然简洁,但是容易引起误导。
使用重载操作符,会造成代码逻辑不清。 除非是非常简单合理的情况,否则应该使用方法而不是操作符重载。
hbjzdsy:顶楼主,顶sibylle

看了好多人回复,不明白,你们都这么牛,怎么没人写出文章来
文章分类
收藏
    相册
    blog图片
    我的照片
    推荐Blog
    CSDN专家群(RSS)
    博客周刊(RSS)
    张孝祥专栏(RSS)
    我的CSDN作家区
    杨洪波(RSS)
    许式伟的专栏(RSS)
    谭振林(RSS)
    银狐999(RSS)
    阿蒙专栏(RSS)
    存档
    软件项目交易
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

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

    新一篇: C++资源之不完全导引(完整版)  | 旧一篇: net_sf_interfacecpp项目诞生宣言

     
                          C++的泛型编程和限制参数类型的技术探讨
     
     
     
    模板概述
    泛型是C++中的重要特性。据说,已经在C++社区中已经取代面向对象成为C++的主要编程泛型。STL和boost库等都广泛使用了泛型。
    泛型,就是C++的模板机制。
    模板可以看作是C++宏的衍生。宏,就相当于是文本文件中的替换。C++编译器在编译前,先把所有使用宏的地方,用宏的定义替换掉宏。
    在Java,.net,ruby等现代语言中都没有宏这种语法的地位。
    宏是另程序变得晦涩难懂的一个原因!我认为在程序中应该尽量避免使用宏!
     
    模板也可以看作是一种模板。C++编译器在编译之前,将创建模板的具体类型的源代码,然后再编译成二进制代码。
     
     
     
    模板技术
     
    模板类的声明和定义,形如:
    template<typename T> class Manage{全部内联函数实现!};
     
     
    函数模版的定义,形如:
     template<typename SequenceT> 
          void trim(SequenceT &, const std::locale & = std::locale());
     
    模板的特化
    模板类的特化
    1)首先定义基泛型:
    template<typename T> class Manage{全部内联函数实现!};
     
    2)然后定义特化的泛型:
    #include 上面基泛型的文件
    template<> class Manage<B>
    {全部内联函数实现!};
     
    特化的泛型必须自己实现所有基泛型定义的成员函数和静态成员。
     
    模板类的成员函数的特化
    如果我们希望特化的泛型继承绝大部分的基泛型的代码。
    那么只需定义特化的成员函数即可!
    在基泛型的定义后面加上特化成员函数:
    template<>
         void Manage<B>::sayHello(void){
         cout<<"B"<<this->t<<endl;
         };
    这个特化的函数就是特化模板类的成员函数。
    实际上,这相当于是隐式定义了上面的那样一个特化模板类,并且所有的基本实现使用基泛型模板的实现!
     
    偏特化/部分特化
    就是一个模板类有多个泛型参数。
    我们特化一个模板参数:
    1)基泛型有多个模板参数:
    #pragma once
    #include "cppunit/extensions/HelperMacros.h"
    #include "B.h"
    #include <iostream>
    using namespace std;
     
    template<typename V,typename T> class Manage
    {
         private:
         T* t;
    public:
         Manage(void){
         this->t=new T();
         };
         void sayHello(void){
         cout<<"管理"<<this->t<<endl;
         };
        
     
     
    public:
         virtual ~Manage(void){};
    };
     
    2)定义的特化有一个还是任意的类型参数
    #pragma once
    #include "Manage.h"
     
    #include "B.h"
    #include <iostream>
    /*相当于
    template<typename V,没有> class Manage<V,B>
    没有对应已有的类型B
    */
    template<typename V> class Manage<V,B>
    {
        
         private:
         B* t;
        
        
    public:
         Manage(void){
         this->t=new B();
         }
         virtual ~Manage(void){};
        
    public:
        
         void sayHello(void){
             std::cout<<"B类"<<this->t<<std::endl;
         };
     
     
    };
     
        但是,请注意,半特化,则没有特化中对应的成员函数的特化那种简单扼要的形式!!!
     
    模板的使用
    使用模板的类应该写在头文件中,并以源码的方式发布
    C++的泛型编程中,需要把所有使用到泛型声明或者定义的代码都直接写在.h头文件中,不能写在.cpp文件中,否则会有很多奇怪的错误!
     
    VC2005也还没有支持分离编译的export关键字!
     
    模板类只能写在一个.h文件中。而且,不可以放在dll项目中。因为模板类是无法导出的!
    导出以后的模板类,只能够在外部声明这个模板类,不能够实际创建模板类的对象!否则会报告
    TestMain.obj : error LNK2019: 无法解析的外部符号"__declspec(dllimport) public: __thiscall net_sf_interfacecpp_core_lang::ObjectRefManage<class AClass>::ObjectRefManage<class AClass>(void)" (__imp_??0?$ObjectRefManage@VAClass@@@net_sf_interfacecpp_core_lang@@QAE@XZ),该符号在函数_main 中被引用
    这样的错误。
    因为,模板类实际上并没能编译成二进制代码。它只是一个宏!需要在编译时根据客户代码的使用情况生成源代码,然后再变成二进制代码。
    因此,作为宏,它应该在.h文件中。作为源代码的元数据,应该共享给用户。因为它需要根据客户的使用情况来生成源代码。因此,它必须在最终客户代码一起!
     
    要使用模板类,就必须把它单独拿出来,把.h这个头文件/源代码交给用户。
    用户在项目中直接作为源代码使用这个头文件,才能够使用这个模板类!
     
     
     
    //确保只被引入系统一次
    #ifndef _net_sf_interfacecpp_core_lang_ObjectRefManage_h_
    #pragma once
    #include "..\net_sf_interfacecpp\IObject.h"
    //下面是自定义的所有.cpp文件都需要引入的头文件
    //#include "ConfigApp.h"
    #include "..\net_sf_interfacecpp\Object.h"
     
    #pragma comment(lib,"..\\debug\\net_sf_interfacecpp.lib")
    /*
    用于管理任意类的实例的生命周期,使之符合IObject接口
    模板类必须定义在头文件中
    NET_SF_INTERFACECPP_API
    */
     
    namespace net_sf_interfacecpp_core_lang{
    template<typename T>
    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 <typename T> class Compare{};
    Template <typename T: Compare > class Vector{};
    BS认为不应该采用这种方式。
    在java中使用模板时,我们经常使用这种方式。
    如:
    Public MyClass<E extends String>{
    ……
    }
     
     
    但,BS认为这种方式不好。而且我在VS2005中也无法编译这样的代码。
    确实,这样会让模板类的数量直线上升。
     
    第二种BS提到的方法非常丑陋。
    就是让每一个方法的实现都转换成我们需要的类型。这样编译时就会报错。
     
    第三种方法,就是使用模板的特化,或者叫做专门化。
    这是BS推荐使用的方法。我也认为应该使用模板特化来限制模板的参数类型。
    尽管BS提出这种语法的本意并不是用来限制模板的参数类型。
    因为,BS根本就不认为应该限制模板的参数类型。偏执的家伙!
    使用模板特化限制模板的参数类型
    作为一个坚定的OO程序员,我是不会容许在自己的C++程序中像STL和boost那样,允许任意参数类型随意使用我的模板类的!
    BS的观点,我不能苟同!
     
    我认为,可以使用模板特化限制模板的参数类型。这种办法是最简单有效的。
    首先,我们定义一个基范型。
     
    然后再在基范型模板类的外部定义几个重载的方法。
    指定如果是我们需要的参数类型,应该执行这些方法。
     
    也可以独立定义特化的模板类。但是,我们上面已经说过了,特化模板类,不如特化模板类的成员函数合算!
     
    最后,我们在基范型的实现中,抛出一个自定义的异常。这样,如果使用了错误的类型,就会抛出异常,导致系统停止运行。我们的客户就可以发现问题所在。
     
    当然,编译时,即使是不正确的类型,还是能够编译通过。只有在运行时才会把错误抓出来。
    编译时检查不出错误,这只能怪BS和C++标准委员会没有为我们提供限制模板的参数类型的语法了。
     
     
    补充:C++的模板和java的模板的异同
    Java5中,也引入的泛型语法。如:
    Public MyClass<E extends String>{
    ……
    }
     
    看上去类似,但是实际实现却非常不同。
    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++的模板要是也这样就好了!
     
     
     
     
     
     
     

    发表于 @ 2008年01月12日 21:45:00|评论(loading...)|编辑

    新一篇: C++资源之不完全导引(完整版)  | 旧一篇: net_sf_interfacecpp项目诞生宣言

    评论

    #GeniusVczh 发表于2008-01-13 10:34:37  IP: 121.33.122.*
    确保只运行一次的代码实际上应该是
    #ifndef MACRO
    #define MACRO/*这句话要写在前面,当然后面也行,只不过写在后面就要要求你#include的那些文件不要犯低级错误*/
    代码
    #endif

    再者,根据资料好像#pragma once已经可以代替上面那几个东西的功能了,好像不需要#ifndef了。不过这个我不是非常清楚,我用的是上面那一种。
    #GeniusVczh 发表于2008-01-13 10:42:28  IP: 121.33.122.*
    关于你所说的模板类限制的问题,我有点看法。

    首先,BS并不是反对限制,事实上他非常希望c++0x的concept可以得到落实,这个就是用来限制类型的。只不过java的方法是通过继承来限制的(也就是传入的参数必须从某个接口或者类继承下来),而concept就不需要了,只需要满足接口的样子就可以了。

    其次,事实上如果你的模板不是通用容器的话,在里头加一个接口的指针模拟java那样子的东西也是好的,因为这样可以让IDE的弹出式列表正确工作,而且可以在编译的时候报错。这个方法也很好,报错的信息相当好看。而且你写代码的时候不需要管模板是怎么实现的,所以只要用户满意就行了,模板的作者大不了以后等c++0x出来了重构一下便是。
    #GeniusVczh 发表于2008-01-13 10:46:25  IP: 121.33.122.*
    再说一两句关于模板的事情。

    只有C++的template才是模板,其他的都是泛型。泛型跟模板的区别就是,泛型是没有计算能力的,但是模板有。模板是图灵完备的。你可以在编译期计算一切可以计算的事情。什么叫一切可以计算的事情呢?意思就是说可以通过有限步骤运算出来的事,通俗一点就是说可以用某种语言譬如Java能够算出来的所有数学问题的集合。C++的模板的优势在于模板类本身也是递归的,其他的语言的template是没有这个能力的,所以只好叫做泛型。
    #shendl 发表于2008-01-13 11:48:30  IP: 58.246.245.*
    #pragma once 确实可以防止文件被编译多次。但我怀疑#pragma once可能是微软自己的宏,为了未来跨编译器,所以使用了上面的格式。
    #pragma once是VC2005自己给我加上的,我就也没有去掉
    #shendl 发表于2008-01-13 11:52:15  IP: 58.246.245.*
    c++0x 我还没研究过。c++0x 已经提出很多年了,有5、6年了吗?不知道什么时候才能实施。搞不好,等C++0X实施,并且被主流编译器支持,还需要20年的时间。所以我也懒得看了!
    我这个人很现实的,只研究立刻用的着的东西。
    #shendl 发表于2008-01-13 11:54:05  IP: 58.246.245.*
    非常感谢GeniusVczh 兄指出 模板和 泛型的区别。
    C++模板的那种用法应该就是 模板元编程 吧? 对此我知之不深。有时间再好好研究。
    #taodm 发表于2008-01-14 16:02:19  IP: 10.61.98.*
    没看过《Modern C++ Design》?用typelist很容易实现类型的检查的。
    接口的(可读出错信息)检查才是concept想要改进的。
    #akirya 发表于2008-01-14 17:29:17  IP: 219.143.47.*
    VS2008的库 升级版都已经有了tr1了.正式版估计也不远了
    #jadedrip 发表于2008-01-14 17:31:46  IP: 218.108.51.*
    现成的东西啊…… 用 Boost::Typeof 库吧
    #shendl 发表于2008-01-28 16:04:05  IP: 211.144.96.*
    开始看《Modern C++ Design》了。模板用得不错!看来,就是使用模板参数实现OO设计模式中的策略模式,或者叫桥接模式。
    现在只看了第一章,等全部看完再写篇文章讨论一下C++的模板。
    #pclili 发表于2008-03-14 15:19:34  IP: 58.61.113.*
    看来你是要好好看一下模板,好好用一下模板了.
    看看Modern C++ Design,有时研究下BOOST的源代码.你会惊叹模板的精妙!
    #feto 发表于2008-04-20 00:19:48  IP: 222.248.57.*
    "如果客户程序员使用了错误的参数类型,那么程序还是能够正常编译。只有在运行到这段代码时,才会报错。"
    请教楼主,这句话不是很明白。需要生成模板的哪些实例都是在编译期决定的,且所有调用到的部分都会做语法分析,若传入的某类型参数不支持某接口,是会报编译错误的,哪有机会躲到运行期才暴露出来?麻烦楼主举个例子?
    #onemonth 发表于2008-05-26 19:15:06  IP: 220.114.22.*
    模板和宏没有什么可以比较的地方。如果非要认为膨胀编译方式和宏相似,我也没有办法。从这篇文章看,博主还在用oo的方式思考模板。
    #gclu212 发表于2008-06-02 15:50:45  IP: 58.247.97.*
    mark
    #shendl 发表于2008-06-04 11:18:31  IP: 220.196.58.*
    已经好久没更新Blog了。 我关于C++范型的观点已经改变了。最近诙谐一篇关于范性的文章。
    #shendl 发表于2008-06-10 00:24:31  IP: 58.39.186.*
    我另外写了2篇Blog,提出了我对于模板的高度评价。《基于对象和面向对象编程范式辨析和主流编程语言中的应用》 http://blog.csdn.net/shendl/archive/2008/06/09/2525785.aspx 和 《主流编程语言优劣考》 http://blog.csdn.net/shendl/archive/2008/06/10/2528560.aspx
    发表评论  


    当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
    Csdn Blog version 3.1a
    Copyright © 良少shendl[Edward Shen]