热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

C++的泛型编程和限制参数类型的技术探讨 模板概述 泛型是C++中的重要特性。据说,已经在C++社区中已经取代面向对象成为C&#43
C++的泛型编程和限制参数类型的技术探讨




模板概述

泛型是C++中的重要特性。据说,已经在C++社区中已经取代面向对象成为C++的主要编程泛型。STL和boost库等都广泛使用了泛型。

泛型,就是C++的模板机制。

模板可以看作是C++宏的衍生。宏,就相当于是文本文件中的替换。C++编译器在编译前,先把所有使用宏的地方,用宏的定义替换掉宏。

在Java,.net,ruby等现代语言中都没有宏这种语法的地位。

宏是另程序变得晦涩难懂的一个原因!我认为在程序中应该尽量避免使用宏!


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




模板技术


模板类的声明和定义,形如:

template<typename T> class Manage{全部内联函数实现&#xff01;};



函数模版的定义,形如&#xff1a;

 template<typename SequenceT>

      void trim(SequenceT &, const std::locale & &#61; std::locale());


模板的特化

模板类的特化

1&#xff09;首先定义基泛型&#xff1a;

template<typename T> class Manage{全部内联函数实现&#xff01;};


2)然后定义特化的泛型&#xff1a;

#include 上面基泛型的文件

template<> class Manage

{全部内联函数实现&#xff01;};


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


模板类的成员函数的特化

如果我们希望特化的泛型继承绝大部分的基泛型的代码。

那么只需定义特化的成员函数即可&#xff01;

在基泛型的定义后面加上特化成员函数&#xff1a;

template<>

     void Manage::sayHello(void){

     cout<<"B"<<this->t<

     };

这个特化的函数就是特化模板类的成员函数。

实际上&#xff0c;这相当于是隐式定义了上面的那样一个特化模板类&#xff0c;并且所有的基本实现使用基泛型模板的实现&#xff01;


偏特化/部分特化

就是一个模板类有多个泛型参数。

我们特化一个模板参数&#xff1a;

1&#xff09;基泛型有多个模板参数&#xff1a;

#pragma once

#include "cppunit/extensions/HelperMacros.h"

#include "B.h"

#include

using namespace std;


template<typename V,typename T> class Manage

{

     private:

     T* t;

public:

     Manage(void){

     this->t&#61;new T();

     };

     void sayHello(void){

     cout<<"管理"<<this->t<

     };

    



public:

     virtual ~Manage(void){};

};


2&#xff09;定义的特化有一个还是任意的类型参数

#pragma once

#include "Manage.h"


#include "B.h"

#include

/*相当于

template<typename V,没有> class Manage

没有对应已有的类型B

*/

template<typename V> class Manage

{

    

     private:

     B* t;

    

    

public:

     Manage(void){

     this->t&#61;new B();

     }

     virtual ~Manage(void){};

    

public:

    

     void sayHello(void){

         std::cout<<"B类"<<this->t<

     };



};


但是&#xff0c;请注意&#xff0c;半特化&#xff0c;则没有特化中对应的成员函数的特化那种简单扼要的形式&#xff01;&#xff01;&#xff01;


模板的使用

使用模板的类应该写在头文件中&#xff0c;并以源码的方式发布

C&#43;&#43;的泛型编程中&#xff0c;需要把所有使用到泛型声明或者定义的代码都直接写在.h头文件中&#xff0c;不能写在.cpp文件中&#xff0c;否则会有很多奇怪的错误&#xff01;


VC2005也还没有支持分离编译的export关键字&#xff01;


模板类只能写在一个.h文件中。而且&#xff0c;不可以放在dll项目中。因为模板类是无法导出的&#xff01;

导出以后的模板类&#xff0c;只能够在外部声明这个模板类&#xff0c;不能够实际创建模板类的对象&#xff01;否则会报告

TestMain.obj : error LNK2019: 无法解析的外部符号"__declspec(dllimport) public: __thiscall net_sf_interfacecpp_core_lang::ObjectRefManage::ObjectRefManage(void)" (__imp_??0?$ObjectRefManage&#64;VAClass&#64;&#64;&#64;net_sf_interfacecpp_core_lang&#64;&#64;QAE&#64;XZ)&#xff0c;该符号在函数_main 中被引用

这样的错误。

因为&#xff0c;模板类实际上并没能编译成二进制代码。它只是一个宏&#xff01;需要在编译时根据客户代码的使用情况生成源代码&#xff0c;然后再变成二进制代码。

因此&#xff0c;作为宏&#xff0c;它应该在.h文件中。作为源代码的元数据&#xff0c;应该共享给用户。因为它需要根据客户的使用情况来生成源代码。因此&#xff0c;它必须在最终客户代码一起&#xff01;


要使用模板类&#xff0c;就必须把它单独拿出来&#xff0c;把.h这个头文件/源代码交给用户。

用户在项目中直接作为源代码使用这个头文件&#xff0c;才能够使用这个模板类&#xff01;




//确保只被引入系统一次

#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")

/*

用于管理任意类的实例的生命周期&#xff0c;使之符合&#xff29;&#xff2f;&#xff42;&#xff4a;&#xff45;&#xff43;&#xff54;接口

模板类必须定义在头文件中

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&#61;(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&#61;new Object();

     this->pT&#61;new T();

     };

    

     long addRef(){

        return this->pIObject->addRef();

     };

     long release(){

      long result&#61;this->pIObject->release();

      if(result&#61;&#61;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&#xff09;模板依赖于我们的dll

2&#xff09;如果我们的类需要使用这个模板&#xff0c;就需要另外建一个dll—ext.dll&#xff0c;包括这个模板&#xff0c;从而间接包括核心dll。

Dll内部时可以使用模板的&#xff0c;因为可以直接在生成dll时根据内部的使用模板的情况&#xff0c;创建源代码&#xff0c;编译成dll。

但是&#xff0c;如果把dll内部的模板发布出去&#xff0c;这就不行了&#xff01;

3)这个模板头文件和dll必须同时提供&#xff0c;避免找不到模板依赖的dll而出错&#xff01;




对模板参数没有限制是一大误区

考察STL和boost中使用泛型的例子。我发现一个问题。使用模板的类&#xff0c;在使用时&#xff0c;程序员可以指定任何类和基本类型。

但是&#xff0c;实际上&#xff0c;很多模板类在代码的内部实现中&#xff0c;对参数类型能够提供的操作实际上是有要求的。如&#xff0c;需要>,<,&#61;等操作是有意义的。

或者需要能够调用某个方法。

但是&#xff0c;STL和boost的库中&#xff0c;均没有对参数进行限制&#xff01;

这样&#xff0c;如果客户程序员使用了错误的参数类型&#xff0c;那么程序还是能够正常编译。只有在运行到这段代码时&#xff0c;才会报错。

甚至&#xff0c;由于STL和boost喜欢使用操作符重载&#xff0c;因此&#xff0c;即使运行时&#xff0c;也不会出错&#xff0c;只是真正的逻辑错了。这样的问题&#xff0c;怎么才能找到错误点呢&#xff1f;我不禁倒吸了一口凉气&#xff01;


翻开C&#43;&#43;之父BS的《C&#43;&#43;语言的设计与演化》一书&#xff0c;BS本人对模板的这一描述&#xff0c;令我乍舌&#xff01;

BS居然认为不需要限制模板的参数类型。认为对模板参数的限制是OOP程序员的偏见&#xff01;

晕&#xff01;C&#43;&#43;是静态编译型语言&#xff0c;不是ruby&#xff0c;python,Javascript这样的动态面向对象语言。

如果ruby开发中&#xff0c;你用了错误类型的对象&#xff0c;执行时没有报错&#xff0c;直到你运行到这段代码才报错&#xff0c;那我也没什么话好说的。人家是解释型语言&#xff0c;放弃了编译检查错误&#xff0c;但换来了语言的巨大动态灵活性。有所得必有所失嘛&#xff01;这我就不说它了&#xff01;

但BS认为C&#43;&#43;不应该限制模板的参数类型&#xff0c;听任错误在运行时爆发&#xff0c;就让我无法理解了&#xff01;


BS&#xff0c;不能因为你对模板的偏爱&#xff0c;让这么多C&#43;&#43;程序陷入危险啊&#xff01;


通过派生对模板的参数类型加以限制的一种方法。

形如&#xff1a;

Template class Compare{};

Template class Vector{};

BS认为不应该采用这种方式。

在java中使用模板时&#xff0c;我们经常使用这种方式。

如&#xff1a;

Public MyClass{

……

}



但&#xff0c;BS认为这种方式不好。而且我在VS2005中也无法编译这样的代码。

确实&#xff0c;这样会让模板类的数量直线上升。


第二种BS提到的方法非常丑陋。

就是让每一个方法的实现都转换成我们需要的类型。这样编译时就会报错。


第三种方法&#xff0c;就是使用模板的特化&#xff0c;或者叫做专门化。

这是BS推荐使用的方法。我也认为应该使用模板特化来限制模板的参数类型。

尽管BS提出这种语法的本意并不是用来限制模板的参数类型。

因为&#xff0c;BS根本就不认为应该限制模板的参数类型。偏执的家伙&#xff01;

使用模板特化限制模板的参数类型

作为一个坚定的OO程序员&#xff0c;我是不会容许在自己的C&#43;&#43;程序中像STL和boost那样&#xff0c;允许任意参数类型随意使用我的模板类的&#xff01;

BS的观点&#xff0c;我不能苟同&#xff01;


我认为&#xff0c;可以使用模板特化限制模板的参数类型。这种办法是最简单有效的。

首先&#xff0c;我们定义一个基范型。


然后再在基范型模板类的外部定义几个重载的方法。

指定如果是我们需要的参数类型&#xff0c;应该执行这些方法。


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


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


当然&#xff0c;编译时&#xff0c;即使是不正确的类型&#xff0c;还是能够编译通过。只有在运行时才会把错误抓出来。

编译时检查不出错误&#xff0c;这只能怪BS和C&#43;&#43;标准委员会没有为我们提供限制模板的参数类型的语法了。



补充&#xff1a;C&#43;&#43;的模板和java的模板的异同

Java5中&#xff0c;也引入的泛型语法。如&#xff1a;

Public MyClass{

……

}


看上去类似&#xff0c;但是实际实现却非常不同。

C&#43;&#43;的模板&#xff0c;是会在编译时&#xff0c;先生成很多新的C&#43;&#43;类。因此&#xff0c;C&#43;&#43;中使用模板有一个问题&#xff0c;就是模板生成的源代码可能太多。引起编译的性能问题。


而java的模板实现机制完全不同。Java模板类在编译时&#xff0c;会“擦除”类型信息。

不会生成新的java类的源代码。

因为&#xff0c;java的类继承体系是单根的&#xff0c;所有类都是Object类的子类。因此&#xff0c;在Java5之前&#xff0c;没有引入模板这个语法之前&#xff0c;java和它的集合实现类也过得很滋润。

Java模板类在编译时&#xff0c;我猜想是这样子的&#xff1a;

1&#xff0c;首先&#xff0c;擦除模板类型的信息&#xff0c;还是使用原来的Object类型。

2&#xff0c;在所有使用模板的参数类型的地方&#xff0c;加上强制类型转换&#xff0c;转换成程序员指定的模板参数类型。


我特别记得BS的一句话&#xff0c;认为特有道理&#xff1a;

他在C&#43;&#43;中特别把不应该使用的语法设计得丑陋&#xff0c;让你不想去使用。如&#xff1a;

dynamic_cast < type-id > ( expression )

动态类型转换。

BS认为&#xff0c;显式的类型转换通常是不必要的。应该避免。


我深深地赞同这句话。Java引入模板&#xff0c;应该就是为了这个原因。现在&#xff0c;写Java代码可以少用很多强制类型转换&#xff01;

用错模板的参数类型&#xff0c;Java编译器都会准确地报告错误。


唉,C&#43;&#43;的模板要是也这样就好了&#xff01;






 

推荐阅读
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 从零学Java(10)之方法详解,喷打野你真的没我6!
    本文介绍了从零学Java系列中的第10篇文章,详解了Java中的方法。同时讨论了打野过程中喷打野的影响,以及金色打野刀对经济的增加和线上队友经济的影响。指出喷打野会导致线上经济的消减和影响队伍的团结。 ... [详细]
  • 本文介绍了指针的概念以及在函数调用时使用指针作为参数的情况。指针存放的是变量的地址,通过指针可以修改指针所指的变量的值。然而,如果想要修改指针的指向,就需要使用指针的引用。文章还通过一个简单的示例代码解释了指针的引用的使用方法,并思考了在修改指针的指向后,取指针的输出结果。 ... [详细]
  • 本文介绍了PE文件结构中的导出表的解析方法,包括获取区段头表、遍历查找所在的区段等步骤。通过该方法可以准确地解析PE文件中的导出表信息。 ... [详细]
  • C++中的三角函数计算及其应用
    本文介绍了C++中的三角函数的计算方法和应用,包括计算余弦、正弦、正切值以及反三角函数求对应的弧度制角度的示例代码。代码中使用了C++的数学库和命名空间,通过赋值和输出语句实现了三角函数的计算和结果显示。通过学习本文,读者可以了解到C++中三角函数的基本用法和应用场景。 ... [详细]
  • 本文介绍了一个程序,可以输出1000内能被3整除且个位数为6的所有整数。程序使用了循环和条件判断语句来筛选符合条件的整数,并将其输出。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • 本文介绍了一个题目的解法,通过二分答案来解决问题,但困难在于如何进行检查。文章提供了一种逃逸方式,通过移动最慢的宿管来锁门时跑到更居中的位置,从而使所有合格的寝室都居中。文章还提到可以分开判断两边的情况,并使用前缀和的方式来求出在任意时刻能够到达宿管即将锁门的寝室的人数。最后,文章提到可以改成O(n)的直接枚举来解决问题。 ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
  • 本文介绍了C++中的引用运算符及其应用。引用运算符是一种将变量定义为另一个变量的引用变量的方式,在改变其中一个变量时,两者均会同步变化。引用变量来源于数学,在计算机语言中用于储存计算结果或表示值抽象概念。变量可以通过变量名访问,在指令式语言中引用变量通常是可变的,但在纯函数式语言中可能是不可变的。本文还介绍了引用变量的示例及验证,以及引用变量在函数形参中的应用。当定义的函数使用引用型形参时,函数调用时形参的改变会同时带来实参的改变。 ... [详细]
author-avatar
云下拾雨
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有