• 网络学院
  • IT资讯
  • 操作系统
  • 网络技术
  • 软件应用
  • 办公软件
  • 编程技术
  • 网站架设
  • 数据库类
  • 平面设计
  • 多媒体类
  • 游戏资讯
  • 教学论文
  • 认证考试
Visual
  站点:
  • 首 页
  • 最新软件
  • 文章教程
  • 国内软件
  • 国外软件
  • 绿色软件
  • 源码下载
  • 字体下载
Visual C++泛型编程实践
软件发布 Visual C++泛型编程实践
网络软件 系统工具 应用软件 联络聊天 图形图像 多媒体类 行业软件 游戏娱乐 编程开发 安全相关 教育教学 数码软件 绿软下载
热门软件: QQ 瑞星 pplive e话通 木马克星 千千静听 office2000 五笔字根 Photoshop 视频分割
返回文章教程首页 >> 文章首页 >> 编程技术 >> C/C++教程 >> Visual C++泛型编程实践

Visual C++泛型编程实践

添加时间: 2006-2-13 9:52:21  作者: C++教程  阅读次数:283   来源: http://d9soft.com

          泛型程序设计(Generic Programming) 是建立在C++的Template机制基础上的一种完全不同于面向对象的程序设计思维模式,STL是泛型概念的一套实作产品。Loki是一个与Boost齐名的开放源码的C++程序库,它通过一些精巧的装置为常规C++开发提供了一些很有用的工具。STL非常好用,弹性非常大,效率也很理想。目前几种主流的C++编译器均有相关的STL实现,而个人认为目前非常流行的Visual c++ 6.0平台中开发文档应用程序时,其文档序列化的功能非常好用,但由于其序列化能力建立在MFC之上,并不被STL支持,所以,如何既拥有STL的效率及通用性,又保留MFC的序列化能力,便成了Visual C++ 6.0平台上运用STL技术开发文档应用程序时不得不面对的一个问题,在这里我就以非常流行的Visual C++ 6.0+SP5平台结合一个假定的例子来介绍一下如何在Visual C++6.0中结合使用STL、Loki及模板技术来开发一个文档应用的开发历程,希望能对大家有所启发。

  示例

  先来简单介绍一下我所用到的例子:一个简单的商务进销存基本应用(不必关注细节),它应该包含:职员(Employee)、产品(Product)、仓库(Storage)、往来单位(Supply)、帐户(Account)、单据(Bill)等等,由于每种信息均应有唯一标识,所以我在这里选用STL中的map来表示如下(为了说明简单起见,我们只列两种):

  

  std::map<size,Employee*> itsEmployees;

  //职员表

  std::map<size,Product*> itsProducts;

  //产品表

  ..

  第一步:实现

  我们将以上map放入多(单)文档应用程序的文档类中,很显然,我们必须对每一个表至少提供以下三种最基本的操作:添加新成员函数、删除指定成员函数、获取指定成员函数。

  对于添加新成员,我们可以实现如下:

  

  size addAccountMember(Account* e); //添加帐户

  {

   //获取下一个可用的ID号

   size id=getNextAccountID();

   itsAccounts[id]=e;

   return id;

  }

  size addEmployeeMember(Employee* e);//添加职员

  {

   //获取下一个可用的ID号

   size id=getNextEmployeeID();

   itsEmployees[id]=e;

   return id;

  }

  ..

  接下来的删除方法仅有一个size(唯一标识)参数,实现如下:

  

  void delAccount(size ID); //删除指定帐户

  {

  itsAccounts.erase(ID);

  }

  void delEmployee(size ID); //删除指定职员

  {

  itsEmployees.erase(ID);

  }

  ..

  获取指定成员的方法如下:

  

  Account* getAccountMember(size ID)

  //获取指定帐户

  {

  return itsAccounts[ID];

  }

  Employee* getEmployeeMember(size ID) //获取指定职员

  {

  return itsEmployees[ID];

  }

  ..

  另外,我们还要为每一个表提供一个获取下一个可用ID的成员函数:

  

  //获取下一个可用职员号

  Size getNextEmployeeID()

  {

   if (itsEmployees.empty())

    return 1;

   std::map<size,Employee*>::iterator it=itsEmployees.end();

   --it;

   return it->first+1;

  }

  //获取下一个可用帐户号

  Size getNextAccountID()

  {

   if (itsAccounts.empty())

   return 1;

   std::map<size,Account*>::iterator it=itsAccounts.end();

   --it;

   return it->first+1;

  }

  ..

  第二步:分析

  以上实现的确达到了我们的设计目的,但仅从直观上来看我就觉得它应该还有改善的空间,最简单的原因:因为它的命名混乱,没有通用性,如:

  

  addAccountMember, addEmployeeMember,..

  delAccount, delEmployee,..

  getAccountMember, getEmployeeMember,..

  getNextAccountID, getNextEmployeeID,..

  对于同一种功能存在这么多不同名称的函数想起来就让我感到可怕,在我们的这个简单的例子中只对6个表实现了三种功能,我们需要为每个表实现4种不同名称的函数,结果,我们需要记住4*6=24个不同名称的函数及它们所对应的功能,如果,如果我们要对更多的表实现更多的功能..,真的不敢相象我们到底要实现多少个不同名称的函数。我想,不用等到函数接口数量爆炸,我的脑子就先爆炸了。如果能够对同一种功能的函数使用一组相同的名字如:

  

  addMember

  delMember

  getMember

  getNextMemberID

  那么,我们的接口名称数量就只与实现的功能多少成常数关系,而与我们要操作的表的个数无关了,整个程序就应该清晰多了。
 第三步:改进(重构)

  重构是一个最近很流行的程序设计思想,说白了就是对已有程序进行改进,在不改变程序外在行为的前提下对程序结构及设计进行改进,以使程序代码更清晰、程序更健壮、更易于维护。

  第一次改进:使用函数重载减少接口名称数量对于添加成员,我们可以直接使用C++的函数重载技术改进如下:

  

  size addMember(Account* e); //添加帐户

  {

   //获取下一个可用的ID号

   size id=getNextAccountID();

   itsAccounts[id]=e;

   return id;

  }

  size addMember(Employee* e); //添加帐户

  {

   //获取下一个可用的ID号

   size id=getNextEmployeeID();

   itsEmployees[id]=e;

   return id;

  }

  这样一来,消除了对不同表进行操作时调用的函数名称的差异,但我们可以看出,这两个函数的操作逻辑是完全一样的,变化的部分与参数相关,这正是模板技术可以发挥作用的地方,但如何将不同的表添加方法与不同的ID号获取方法及对应的map联系起来呢?

  我们再来看删除函数:由于不同表的删除方法均只有一个相同类型的参数size ID,而函数重载必须要有不同的参数列表,所以,要想实现一个void delMember(size ID)分别对应不同的表的删除操作好象是不可能的,getMember(size ID)方法也是一样,它对不同的表操作虽然有不同的返回值,但参数也是一样的,所以,也不能运用C++内的函数重载方法来实现函数接口命名的一致化。而获取下一个可用ID的函数方法甚至连参数都没有,怎么办呢?看来我们没有办法了。

  幸运的是,Andrei Alexandrescu在他的《 C++设计新思维——泛型编程与设计模式之应用》一书中为我们提供了一种解决办法: Type2Type——它是一个可用于代表参数类型,以让你传递给重载函数的轻量级的ID,其定义如下:

  

  Template <typename T>

  Struct Type2Type

  {

  typedef T OriginalType;

  };

  它没有任何数值,但其不同型别却足以区分各个Type2Type实体,而这正是我们所要的。现在,让我们来先解决addMember成员函数中的获取下一个可用ID号的函数,我们可以定义一个重载的函数如下:

  

  size getNextMemberID(Loki:: Type2Type<Employee>)

  //对应职员操作

  {

   if (itsEmployees.empty())

    return 1;

   std::map<size,Employee*>::iterator it=itsEmployees.

   end();

   --it;

   return it->first+1;

  }

  size getNextMemberID(Loki:: Type2Type<Account>)

  //对应帐户操作

  略..

  相应的,删除类函数定义如下:

  

  void delMember(size ID, Loki:: Type2Type<Account>)

  void delMember(size ID, Loki:: Type2Type<Employee>)

  获取类函数定义如下:

  

  Account* getMember(size ID, Loki:: Type2Type<Account>)

  Employee* getMember(size ID, Loki:: Type2Type<Employee>)

  这样,我们的函数接口就比刚开始的方法更清晰,我们的大脑中要记住的函数名就要少多了。

  第二次改进:使用模板技术减少接口函数数量经过第一次的改进,我们的接口结构比初始的方案要更清晰,但它似乎还存在一个问题:软件大师Martin Fowler在他的著作《重构——改善既有代码的设计》中将之列为代码的坏味道之首——代码重复。我们可以看到,添加、删除、获取的函数实现中,几乎完全是一样的实现逻辑,只不过所操作的map变量不同而已,如下(以添加为例):

  

  size addMember(Account* e); //添加帐户

  {

   //获取下一个可用的ID号

   size id= getNextMemberID(Loki::Type2Type<Account>());

   itsAccounts[id]=e;

   return id;

  }

  size addMember(Employee* e); //添加帐户

  {

   //获取下一个可用的ID号

   size id=getNextMemberID(Loki::Type2Type<Employee>());

   itsEmployees[id]=e;

   return id;

  }

  如果我们能有办法根据不同的参数获得不同的要操作的map变量,那么这两个方法完全可以实现为一个模板方法如下:

  

  template<typename T>

  size addMember(T* e)

  {

   size empid=getNextMemberID(Loki::Type2Type<T>());

   //关键在于以下函数

   std::map<size,T*>& its=getMap(Loki::Type2Type<T>());

   its[empid]=e;

   return empid;

  }

  如果getMap()方法能实现,那么,我们的模板方法就可以成功。有了前面的铺垫,这个应该水到渠成:

  

  std::map<size,Account*>& getMap(Loki::Type2Type<Account>)

  {

   return itsAccounts;

  }

  std::map<size,Employee*>& getMap(Loki::Type2Type

  <Employee>);

  {

   return itsEmployees;

  }

  这样我们就可以将所有的添加、删除、获取函数进行模板化实现如下:

  

  template <typename T>

  size getNextMemberID(Loki::Type2Type<T>)

  {

   std::map<size,T*>& its=getMap(Loki::Type2Type<T>());

   if (its.empty())

    return 1;

    std::map<size,T*>::iterator it=its.end();

    --it;

    return it->first+1;

  }

  template <typename T>

  size addMember(T* e)

  {

   size empid=getNextMemberID(Loki::Type2Type<T>());

   std::map<size,T*>& its=getMap(Loki::Type2Type<T>());

   its[empid]=e;

   return empid;

  }

  template <typename T>

  T* getMember(size memberID,Loki::Type2Type<T>)

  {

   std::map<size,T*>& its=getMap(Loki::Type2Type<T>());

   return its[memberID];

  }

  template <typename T>

  void delMember(size memberID,Loki::Type2Type<T>)

  {

   std::map<size,T*>& its=getMap(Loki::Type2Type<T>());

   its.erase(memberID);

  }

  这样,对于本例中6个表分别实现添加、删除、获取成员三组方法,我们总共需要用:四个模板化函数、以及一组分别针对6个表的getMap重载函数。然后,我们每增加一个表,只需要为getMap方法添加一个重载的实现,与初始设计中的4*6=24种名称各不相同,每增加

  一个表支持,要添加4种不同名称的函数实现的方案比较起来,是不是更清晰、更易维护、易于扩展了呢?

  第四步:添加序列化支持

  在本文开头我提到:Visual c++ 6.0平台中开发文档应用程序时,其文档序列化的功能非常好用,但由于其序列化能力建力在MFC之上,并不被STL支持,如何既拥有STL的效率及通用性,又保留MFC的序列化能力呢?由于篇幅的限制,我以下就只讲怎么做,而不讲为什么了(参见《MFC深入浅出》)。

  在这里我们假定map所包含的对象已具备序列化的能力,那么,对于一个map来说,其序列化实现应该如下(以Account 为例):

  

  void SerializeMap(CArchive& ar,std::map<size,Account*>&map)

  {

   typedef std::map< size,Account*>::value_type

   value_type;

   typedef std::map< size,Account*>::iterator iterator;

   if (ar.IsStoring())

   {

    DWORD n=map.size();

    ar.WriteCount(n);

    for(iterator it=map.begin();it!=map.end();++it)

    {

     ar<<it->first<<it->second;

    }

   }

   else

   {

    size first;

    Account* second;

    DWORD nNewCount=ar.ReadCount();

    while (nNewCount--)

    {

     ar>>first>>second;

     value_type value(first,second);

     map.insert(value);

    }

   }

  }

  将其中的型别相关信息提取出来,利用模板技术就得到一个map的序列化支持函数如下:

  

  template <typename Key,typename T>

  void SerializeMap(CArchive& ar,std::map<Key,T>& map)

  {

   typedef std::map<Key,T>::value_type value_type;

   typedef std::map<Key,T>::iterator iterator;

   if (ar.IsStoring())

   {

    DWORD n=map.size();

    ar.WriteCount(n);

    for(iterator it=map.begin();it!=map.end();++it)

    {

     ar<<it->first<<it->second;

    }

   }

   else

   {

    Key first;

    T second;

    DWORD nNewCount=ar.ReadCount();

    while (nNewCount--)

    {

     ar>>first>>second;

     value_type value(first,second);

     map.insert(value);

    }

   }

  }

  这样,我们只需要在文档类的序列化函数中如下调用:

  

  SerializeMap(ar,itsEmployees);

  SerializeMap(ar,itsAccounts);

  ........

  即可拥有MFC内置的序列化能力了。

 

上下文章:

 

上一篇文章: C/C++ 跨平台I/O操作技巧 下一篇文章: C++编程从零开始之赋值操作符

相关文章:

  • Oracle专家高级编程学习笔记[1]
  • Oracle专家高级编程学习笔记[2]
  • EMC存储技术上使用ASM的最佳实践白皮书
  • EMC存储技术上使用ASM的最佳实践白皮书
  • Oracle9i OCM认证实践课考试说明

相关软件:

  • Visual Studio 2005 Express Editions (Visual Basic)
  • Visual Graph专业图形引擎 V4.6
  • Visual Basic V5.0 Runtime Files
  • 全国计算机等级考试模拟软件(2006年全年使用)二级Visual Basic V9.0
  • VSh Visual Bookmarks V4.2 final
  • Visual localize .net V5.34

 

快速导航

  • 网络学院
  • 精品汇聚
  • 字体下载
  • 教程下载
  • ASP源码
  • PHP源码
  • Net源码
  • JSP 源码

编程技术分类导航

  • ASP & ASP.NET教程
  • PHP教程
  • JSP教程
  • C/C++教程
  • VB & VB.NET教程
  • VC教程
  • Delphi教程
  • BCB教程
  • VFP教程
  • PB教程
  • JAVA教程
  • XML教程
  • C#教程
  • CGI教程

本类经典文章推荐

  • C/C++作用域引申出的编码规范
  • 程序宝典:C++学习感想
  • C语言:黑客学员必修课(一)
  • C语言:黑客学员必修课(二)
  • 使用多线程实现数据实时采集
  • C++编程中的四个调试小技巧
  • C语言程序设计基础之联合
  • 学C++不得不看的一篇文章
  • C,曾经的程序员梦
  • 水滴石穿C语言之可变参数问题

C/C++教程阅读排行

  • 学C++不得不看的一篇文章
  • 水滴石穿C语言之可变参数问题
  • C语言:黑客学员必修课(一)
  • 程序宝典:C++学习感想
  • 我的Visual C++入门之路
  • 初学者学习C++的50条忠告
  • C++中的struct专题研究
  • C++箴言:只要可能就用const
  • 强大的语言——C入门
  • C++编程中的四个调试小技巧

编程技术阅读总排行

  • VB入门教程之一
  • Java连接数据库实例
  • 第二章 PowerBuilder 入门之创建新...
  • VC++之List Box/Check List Box控...
  • 第一章 什么是PowerBuilder
  • VC++ List Ctrl控件
  • VC++ Combo Box/Combo Box Ex控件
  • 学C++不得不看的一篇文章
  • VB入门教程之二
  • VC++之Button控件

广告位置

字母检索 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 回到顶部

关于我们 | 版权声明 | 免责条款 | 广告联系 | 软件发布 | 下载帮助 | 下载排行 | 网站地图 | 特别鸣谢 | 友情连接

copyright; 2005-2008 D9soft.com 第九软件网 版权所有