1 / 48

三. 从 C++ 到 COM

三. 从 C++ 到 COM. 源代码形式的重用 动态链接库 接口类与实现类的分离 抽象基类作为二进制接口 实现方式 内存泄漏 接口类与实现类的内存结构 对象的扩展性 功能扩展的需求 方法一:扩展接口 方法二:多接口 中性的类型转换 资源管理. 1 源代码形式的重用. C++ 的一个主要目标:用户自定义类型 UDT 并且可以在其他环境下重用。 类库市场 OWL MFC ATL VCL Qt 主要问题: 白盒子,客户程序和类库之间过度的耦合 重用性的主要障碍 编译与链接模型

kasia
Download Presentation

三. 从 C++ 到 COM

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 三. 从C++到COM • 源代码形式的重用 • 动态链接库 • 接口类与实现类的分离 • 抽象基类作为二进制接口 • 实现方式 • 内存泄漏 • 接口类与实现类的内存结构 • 对象的扩展性 • 功能扩展的需求 • 方法一:扩展接口 • 方法二:多接口 • 中性的类型转换 • 资源管理

  2. 1 源代码形式的重用 • C++的一个主要目标:用户自定义类型UDT并且可以在其他环境下重用。 • 类库市场 OWL MFC ATL VCL Qt • 主要问题: • 白盒子,客户程序和类库之间过度的耦合 • 重用性的主要障碍 编译与链接模型 • 以下各节将演示C++的对象模型在“构造可重用的组件”时所遇到的挑战.

  3. 一个例子:某库厂商开发一个算法,能在常时间内进行子串的查找工作。特点是其查找时间与目的串的长度无关。一个例子:某库厂商开发一个算法,能在常时间内进行子串的查找工作。特点是其查找时间与目的串的长度无关。 • 该厂商创建了一个字符串类。为此,他生成了一个头文件和一个实现文件: //faststring.h class FastString{ char * m_psz; //保存原始的字符串 public: FastString(const char*psz); //构造函数 ~FastString(void); int Length(void)const; //返回该字符串的长度 int Find(const char*psz)const; //查找指定的子串 };

  4. //faststring.cpp #include "faststring.h" #include<string.h> FastString::FastString(const char* psz) :m_psz(new char[strlen(psz)+1]) {strcpy(m_psz,psz);} //分配内存 FastString::~FastString(void) { delete []m_psz;} //释放内存 int FastString::Length(void) const{ return strlen(m_psz);} //计算长度 int FastString::Find(const char*psz)const {;} //省略, 这不是我们讨论的重点.

  5. 客户使用方法 在客户应用中包含faststring.h faststring.cpp #include "faststring.h" void f() { FastString *pFS=new FastString; ...... } 缺陷:模块化特征消失殆尽 • 无法更新,升级,除非代码更换 • 代码更新后必须重新编译 • 所有的技术公开。

  6. 2 动态链接库 • 静态链接 • 许多类库的做法 • 编译时刻的链接 • 静态链接的缺点 • 代码重复:多个程序各有自己的代码,需要更多的内存 • 客户程序占据更多的外存空间 • 库代码更新需要重新编译所有的客户程序 • 动态链接 • 运行时刻的链接 • 动态链接形式 • 编译时刻通过引入库 • 运行时刻完全动态

  7. 使用动态链接库的方式可以解决上述问题 把FastString的所有方法从DLL中引出去 //faststring.h Class__declspec(dllexport) FastString // __declspec(dllexport)是C++编译器指示符, 它通知编译器, 此类的所有成员方法都将对外输出.以下代码见FastStringDLL { char * m_psz; public: FastString(const char*psz); ~FastString(void); int Length(void)const; int Find(const char*psz)const; } • FastString的所有方法都将被加载到dll的引出表中。允许在运行时把每个方法的名字解析到内存中对应的地址。 • 链接器产生一个引入库 (import library),这个库暴露了FastString成员的符号,并没有包含实际的代码,它只是简单地包含一些引用,这些引用指向dll的文件名和被引出的符号的名字。当客户链接引入库时,有一些存根会被加入到可执行文件中,它在运行时通知装载器装载dll并且把所有被引入的符号解析到内存中相应的位置上。

  8. 客户的使用方法(VC环境下): • 静态加载 • 1.#include FastStringDLL.h 2. Setting -> link->加入 FastStringDll.Lib(VC6) 项目-》属性-》配置属性-》链接器-》输入-》附加依赖项 加入FastStringDll.Lib(VS.NET) 3。FastStringDll。Dll放在可访问的路径中,特别地,放在一起。 4。代码:FastString *pFS=new FastString("asdfg"); int i=pFS->Length(); • 动态加载 对于输出类的情形不能使用动态加载。输出函数可以使用动态加载。 • 名字改编问题: • 使用了extern”c”保护的全局函数(vc环境下)对生成的引入库使用coff2omf工具转换后bcb可以顺利链接。 但是,对于未使用extern ‘C’保护的函数不能顺利链接,即使使用动态加载也不行,GetProcAddress函数将失败。类的成员函数不能使用extern ‘C’ 对其提供保护。因此上述代码在bcb下无法顺利链接. (如前章所述,使用DEF文件对引出的函数添加别名的方式,可以勉强解决这个问题,但是十分繁琐,不具备通用性)

  9. 优点: 1。有效的技术保密 2。减少客户代码尺寸、节省资源 3。在特定的条件下(头文件保持不变时),客户可以顺利升级(无需重新编译,直接更新DLL)。 • 缺点: 1。名字改编问题,限定了编译器的使用。 2。在一般情况下,升级困难。见下节。

  10. DLL 的升级 • 一个经常发生的情形是,库厂商要对库函数、类的实现进行升级。假设现在它们找到了一个更好的算法。 • FastStringDLL2.0版。 //FastStringDLL.h v 2.0 class FASTSTRINGDLL_API FastString{ const int m_cch;//v2.0 为了支持这个新的算法, 必须增加一个成员变量,以存储字符串的长度. 在构造函数中被赋值, 以后,当客户查询长度时,直接返回,无需计算. 导致了头文件的更新. char * m_psz; public: FastString(const char*psz); ~FastString(void); int Length(void)const; int Find(const char*psz)const; }

  11. // FastStringDLL.cpp v2.0 FastString::FastString(const char* psz) :m_cch(strlen(psz)),/*长度保存下来*/m_psz(new char[m_cch+1]) {strcpy(m_psz,psz);} FastString::~FastString(void) { delete []m_psz;} int FastString::Length(void) const{ return m_cch;}// 直接返回, 无需计算, 提高了效率. int FastString::Find(const char*psz)const {//;省略 return 0; }

  12. 2.0版编译、发布。 • 新的客户使用新的头文件和新的DLL。 • 老的客户使用老的头文件和老的DLL。 • 在新老DLL共存的情形下,由于类的定义发生了改变,这些应用系统很有可能产生不兼容。配置混乱、维护艰难。 • 版本问题的根源在于C++的编译模型,这种模型不能支持独立的二进制组件的设计。客户必须准确地知道对象的布局结构,从而导致客户和库的强的耦合关系。客户与库的紧密耦合性使得在不重新编译客户的情况下,无法实现类的替换。 • 升级不是无缝的。

  13. 3 接口类与实现类的分离 • 方法思路: 构造一个模型,把两个抽象的概念(即接口和实现)做成两个分离的实体,即C++类,定义一个C++类使得它代表指向一个数据类型的接口;定义另一个C++类作为数据类型的实际实现,对象的实现者可以修改类的细节,而接口类保持不变。使用接口类屏蔽实现类,不会向用户暴露任何实现细节。

  14. //faststringitf.h Class __declspec(dllexport) FasStringItf // 输出的是接口类而不是真正的实现类. 接口类是实现类的一个封装.接口类的定义更容易保持不变性. { class FastString; // 声明实现类, 但是不需要包含实现类的头文件 FastString* m_pThis; //实现类的指针. Public: FastStringItf(const char*psz); ~FastStringItf(void); int Length(void)const; int Find(const char*psz) const; }; 此接口类的二进制布局不会随着实现类的数据成员或方法成员的增删而改变,从而有效地将实现部分隐藏起来,客户的编译器不需要知道实现类的细节,(不需要包含实现类的头文件,因此实现者不必公布) 接口类的方法成为了Dll的入口点,接口方法的实现只是把方法的调用传递给实现类,如下:

  15. //faststringitf.cpp #include “faststringitf.h” #include “faststring.h”FastStringItf::FastStringItf(const char *psz):m_pThis(new FastString(psz)) assert(m_pThis!=0);} FastStringItf::~FastStringItf(void){ delete m_pThis;} int FastStringItf::Length(void) const{ return m_pThis->Length();} int FastStringItf::Find(const char*psz)const{ return m_pThis->Find(psz);} 客户的使用方法与前相仿,创建一个接口类对象,而非实现类对象,如下:

  16. 客户的使用方法 • 1. #include “faststringitf.h” //包含接口类头文件,而非实现类的. 2. Setting -> link->加入 FastStringDll.Lib(VC6) 项目-》属性-》配置属性-》链接器-》输入-》附加依赖项 加入FastStringDll.Lib(VS.NET) 3。FastStringDll。Dll放在可访问的路径中,特别地,放在一起。 4。代码:FastStringItf *pFSF=new FastStringItf("asdfg"); //显示地创建接口类,而非实现类,当然,内部创建了实现类 int i=pFSF->Length(); //通过接口类调用实现类 • 实现者可以灵活改变其实现方法,而不会影响到现有的客户。 • 以上方法,较好地解决了版本升级的问题,不仅如此,最重要的是提供了一种思路. 虽然还很不完善. • 缺陷: • 对于大的类库,成百上千的函数,都需要机械地进行调用的转移,冗长。 • 每个方法增加两次调用,对于关键的应用性能上不理想。 • 链接器的兼容性仍然没有解决。

  17. 4 抽象基类作为二进制接口 • 4.1 实现方式 • 前节中,接口类通过保留一个实现类的指针的方式来调用实现类的方法.虽然取得重大进展,但显得笨拙. C++允许我们使用更好的方式:虚函数. • 假定在给定的平台上所有的编译器都使用同样的方法实现虚函数的调用机制,我们可以这样来定义C++的接口类:所有的公共操作都定义为虚函数,以保证所有的编译器为客户端的方法调用产生等价的机器码。 创建步骤: 1。Vc下新建一个dll工程,头文件中声明接口类,并以Extern’C’的方式引出一个创建接口类的函数。 //IFastString.h class IFastString { public: virtual int Length(void) const=0; virtual int Find(const char*psz)const=0;}; extern "C" { __declspec(dllexport) IFastString *CreateFastString(const char*psz);}

  18. 2。新建一个实现类FastString,派生自接口类IFastString,类的声明以及实现分别在文件FastString.h 和FastString.cpp中 • class FastString : public IFastString { const int m_cch; char*m_psz; public: FastString(const char* psz); ~FastString(); int Length(void)const; //不需要加上virtual 关键字 int Find(const char*psz)const; }; 实现类的定义与前面的一致,只是派生自接口类,因此它的两个函数是虚函数。

  19. 实现代码如下: IFastString * CreateFastString(const char * psz) {return new FastString(psz);} //创建一个实现类的实例. 返回值确实接口指针. FastString::FastString(const char* psz) :m_cch(strlen(psz)), m_psz(new char[m_cch+1]) {strcpy(m_psz,psz);} FastString::~FastString(void) { delete []m_psz;} // 释放成员变量占用的内存. int FastString::Length(void) const{ return m_cch;} int FastString::Find(const char*psz)const {// 具体方法省略 } //实现方法与前面完全一致, 所不同的是成员函数的性质变了.

  20. Dll的四个文件:接口类的声明和实现,实现类的声明和实现,只有接口类的声明需要提供给客户。Dll只引出一个函数CreateFastString。Dll的四个文件:接口类的声明和实现,实现类的声明和实现,只有接口类的声明需要提供给客户。Dll只引出一个函数CreateFastString。 • 客户的使用方法(在C++Builder下) 新建一个工程,加入接口类的头文件,加入FastString.lib 引入文件。 #include “IFastString.h”//客户需要接口类的声明文件. IFastString *pIFS=CreateFastString(“abcd"); //创建一个实现类的实例,用一个接口类的指针指向它. pIFS->Find(“a”); //通过接口类来调用实现类的功能. delete pIFS; 可以圆满解决链接器的兼容性问题。

  21. 4.2 内存泄漏问题 • 以上代码会导致内存泄漏,原因在于接口类的析构函数不是虚函数. delete pIFS;将导致接口类(基类)的析构函数被调用.而实现类(派生类)中所动态分配的保存字符串的内存将不能得到及时释放. • 简单的解决方法: 把基类的析构函数声明为虚的.这样,派生类的析构函数也是虚的. 执行delete pIFS;时,由于pIFS指向的是派生类的对象,所以调用的是派生类的析构函数. 而删除派生类的对象时,析构函数的执行顺序为: • 派生类本身的析构函数 • 对象成员的析构函数 • 基类的析构函数 因此派生类析构函数将递归地自动激活基类的析构函数.通过这样的方法可以完整地释放派生类对象所占用的存储空间. • 但是如果将接口类的析构函数做成虚的,会破坏接口类的编译器独立性。因为虚析构函数在虚表中的位置随着编译器的不同而不同。 • 解决方法:给接口类增加一个Delete方法,作为其另一个纯虚函数,在派生类中实现自身的删除,以导致正确的析构过程。

  22. 于是, 接口类的声明变为 class IFastString{ public: virtual void Delete(void)=0; virtual int Length(void) const=0; virtual int Find(const char*psz)const=0; }; 实现类的声明 class FastString : public IFastString { int m_cch; char*m_psz; public: FastString(const char* psz); ~FastString(); void Delete(void); int Length(void)const; int Find(const char*psz)const; };

  23. IFastString * CreateFastString(const char * psz) {return new FastString(psz);} //创建一个实现类的实例. 返回值确实接口指针. void FastString::Delete() { delete this; } //由用户调用 删除自身. FastString::~FastString(void) { delete []m_psz;} // 由上一个函数引起 释放成员变量占用的内存. //构造函数, Length Find 等不变,略. 使用方法(静态加载): • IFastString *pIFS=CreateFastString(“abcd"); pIFS->Find(“a”); pIFS ->Delete() ;//调用的是实现类的函数实现.

  24. 使用方法(动态加载) IFastString *pIF; HANDLE h; h=LoadLibrary("FastString.dll"); if(h==NULL){ShowMessage("load err");return;} typedef IFastString *(__stdcall *PF)(void); PF pf; pf=(PF)GetProcAddress(h,“CreateFastString"); pIF= pf(“abcd”); //或pIF=(*pf)(“abcd”); pIF ->Length(); //……使用接口 pIF->Find(“a”); pIF->Delete(); FreeLibrary(h);

  25. Delete(null) this vptr Length(null) Find(null) IFastString this vptr FastString::Delete m_cch FastString:: Length m_psz FastString:: Find FastString 接口类与实现类的内存结构 4.3 接口与实现类的内存结构

  26. 通过这种方式我们可以安全地在C++环境中暴露DLL中的类,而在另一个C++环境中访问它。接口类作为客户与实现类的屏蔽. 这种方式是COM中建立与编译器厂商无关的可重用组件的技术基础。

  27. 5 对象的扩展性 • 5.1 功能扩展的需求 • 至此客户可以动态地加载二进制组件,在接口类定义不变的情况下,实现者可以自由地对实现方案进行升级,客户无需重新编译。至此已经解决了前面所面临的链接器兼容性问题和类库的更新问题。 • 但是对象的接口却不能变化,因为客户在编译过程中需要接口的精确定义,对接口的任何变化都会导致客户的重新编译。而且对接口的任何改编都会破坏对象的封装性要求。 • 但是, 对功能的需求却是无限的,如果擅自更改已经发布的接口类的话……

  28. 初始IFastString接口: class IFastString{ public: virtual void Delete(void)=0; virtual int Length(void) const=0; virtual int Find(const char*psz)const=0; }; 更改后的接口: class IFastString{ public: virtual void Delete(void)=0; virtual int Length(void) const=0; virtual int Find(const char*psz)const=0; virtual int FindN(const char*psz,int n)=0; //新增加的虚函数。 }; 老客户得到了包含FindN表项的新对象时,仍然能正常工作,然而,新客户如果碰巧使用了老对象,当客户针对基于老接口定义编译得到的对象调用FindN时,程序崩溃了。 • 因此,接口定义一旦公开,是不能更改的。 功能扩展性解决方案:允许实现类暴露多个接口。这有两种途径: 1。设计一个接口使得它继承自另一个相关的接口。 2。让实现类继承多个不相关的接口。

  29. 5.2 方法一:扩展接口 对原接口进行扩展,生成新的接口.比如使用从原接口继承的方式(也可以使用别的方式,如嵌套类) class IFastString2:public IFastString { public: virtual int FindN(const char*psz,int n)=0;}; 新的实现类的声明: (派生自新接口) class FastString : public IFastString2 { int m_cch; char*m_psz; public: FastString(const char* psz); ~FastString(); void Delete(void);// IFastString的方法 int Length(void)const; int Find(const char*psz)const; int FindN(const char*psz,int n);// IFastString2的方法 }; 而客户在使用时:

  30. 客户在运行时询问对象,以确认对象是否支持新的接口。这里将使用dynamic_cast 运算符。 int Find10thBob(IFastString *pfs) { IFastString2 *pfs2=dynamic_cast<IFastString2 *>(pfs); if(pfs2) return pfs2->FindN("Bob",10); //如果实现了IFastString2 接口. 新实现类(如果使用的是新的服务器) else error("can not find 10th occurrence of Bob"); //如果实现了IFastString2 接口. 老实现类(如果使用的是老的服务器) } 这种平滑的由用户决定的downcast能力,可以使得系统功能不断地扩展、升级。

  31. 5.3 方法二:多接口 • 前一节新接口扩展的功能与原功能有一定的逻辑关系. • 当对象需要提供与原功能不相关的新功能时,比如说提供永久性支持,即在外部存储设备的存储能力。当然也可以仿照上面的办法创建一个新的接口派生自IFastString: class IPersistentObject:public IFastString{ Public: Virtual bool Load(const char *pszFileName)=0; Virtual bool save(const char *pszFileName)=0; } 但是其他非IFastString兼容的对象(不需要支持IFastString接口的对象),也可能需要永久性支持,那么按照这种方案,对象支持IPersistentObject接口, 则这些对象也要支持Length、Find等操作,而这些操作对它很可能并没有用处。

  32. 因此,为了尽可能使得IPersistentObject接口具有通用性,它应该是一个独立的接口,而不是派生自IFastString:因此,为了尽可能使得IPersistentObject接口具有通用性,它应该是一个独立的接口,而不是派生自IFastString: • Class IPersistentObject{ Public: virtual void Delete()=0; //使对象能够删除自身 virtual bool Load(const char *pszFileName)=0; //实际功能 virtual bool Save(const char *pszFileName)=0;} 而实现类要同时支持此两个接口:

  33. 实现类的声明: (同时派生自两个类) class FastString : public IFastString ,public IPersistentObject { int m_cch; char*m_psz; public: FastString(const char* psz); ~FastString(); void Delete(void);// 一般的方法,虚表中有两项,同时改写 int Length(void)const; // IFastString的方法 int Find(const char*psz)const; bool Load(const char *pszFileName); // IPersistentObject的方法 bool Save(const char *pszFileName); };

  34. 客户的使用方法:假如客户得到了IFastString指针,如果客户想要操作IPersistentObject提供的功能,只需要使用RTTI得到一个指向对象暴露出来的IPersistentObject接口即可:客户的使用方法:假如客户得到了IFastString指针,如果客户想要操作IPersistentObject提供的功能,只需要使用RTTI得到一个指向对象暴露出来的IPersistentObject接口即可: Bool SaveString(IFastString*pfs,char *pszFN) { bool bResult=false; IPersistentObject *ppo=dynamic_cast<IPersistentObject*>(pfs); // 此处是cross cast If(ppo) {bResult=ppo->Save(pszFN); //如果是新对象 return bResult;} else ….. //如果是老对象 } • 以上代码可以工作,因为编译器具有关于实现类的布局结构和类型层次的足够多的信息以确定对象是否继承自IPersistentObject。

  35. 5.4 中性的类型转换 • 每个编译器厂商对于RTTI的实现是不相同的,这破坏了以抽象基类作为接口而获得的编译器的独立性。 • 我们可以中性地处理dynamic-cast的语义,不使用与编译器相关的语言特征。从每个接口显示地暴露出一个广为人知的方法,来完成与dynamic-cast语义等价的功能,而不强求各方使用同样的编译器。 • 这样两个接口的定义变为:

  36. Class IPersistentObject{ Public: virtual void * Dynamic_cast(const char*pszType)=0; //注意Dynamic_cast是一个函数而不是操作符 virtual void Delete()=0; //删除操作 virtual bool Load(const char *pszFileName)=0; //功能 virtual bool Save(const char *pszFileName)=0;} • Class IFastString{ Public: virtual void * Dynamic_cast(const char*pszType)=0; virtual void Delete()=0; virtual int Length()=0; virtual int Find(const char*psz)=0;} 我们注意到两个接口都提供了dynamic_cast方法和delete方法。很自然地,我们把这两个方法提升到一个基类中, 于是:

  37. Class IExtensibleObject{ //实现通用的功能 Public: virtual void * Dynamic_cast(const char*pszType)=0; //转换 virtual void Delete()=0;} //删除 • Class IPersistentObject: IExtensibleObject{ Public: //实际功能 virtual bool Load(const char *pszFileName)=0; virtual bool Save(const char *pszFileName)=0;} • Class IFastString: IExtensibleObject{ Public: //实际功能 virtual int Length()=0; virtual int Find(const char*psz)=0;} 而实现类的定义如下:

  38. class FastString : public IFastString ,public IPersistentObject { int m_cch; char*m_psz; public: FastString(const char* psz); ~FastString(); void * Dynamic_cast(const char*pszType)//一般的方法 void Delete(void); int Length(void)const; //IFastString的方法 int Find(const char*psz)const; bool Load(const char *pszFileName); // IPersistentObject的方法 bool Save(const char *pszFileName); };

  39. FastString的Dynamic_Cast方法的实现通过操纵对象的类层次结构,模拟出RTTI的功能。因为实现类是从接口类派生出来,所以可以使用显示的静态类型转换,把this指针转换到客户所请求的类型。FastString的Dynamic_Cast方法的实现通过操纵对象的类层次结构,模拟出RTTI的功能。因为实现类是从接口类派生出来,所以可以使用显示的静态类型转换,把this指针转换到客户所请求的类型。 • Void *FastString::Dynamic_Cast(const char *pszType){ If(strcmp(pszType,”IFastString”)==0) return static_cast<IFastString*>(this); else If(strcmp(pszType,”IPersistentObject”)==0) return static_cast<IPersistentObject*>(this); else If(strcmp(pszType,”IExtensibleObject”)==0) return static_cast<IFastString*>(this); Return 0;} //未支持接口的请求。 • static_cast仅仅是在对象与子对象之间进行偏移的加减.是编译器中性的. dynamic_cast要改变虚表指针所指向虚表的可见部分,其实现方式是编译器相关的.

  40. IExtensibleObject IExtensibleObject IFastString IPersistentObject FastString • FastString的类型层次结构

  41. 从IFastString继承来的虚表 this vptr FastString::Dynamic_cast FastString::Delete vptr FastString::Length M_cch FastString::Find M_psz FastString::Dynamic_cast 从IFastString继承来的虚表 FastString::Delete FastString::Load FastString::Save FastString 对象的二进制布局结构

  42. 6 资源管理 • 考虑以下客户代码: • void f(void){ IFastString *pfs=0; IPersistentObject *ppo=0; pfs=CreateFastString("asdf"); if(pfs) { ppo=(IPersistentObject*) pfs->Dynamic_Cast("IPersistentObject"); if(!ppo) pfs->Delete();//转换不成功,使用pfs来删除对象。 else{ ppo->Save("c:\\myfile"); ppo->Delete();} //转换成功,使用ppo来删除对象。 } }

  43. 以上代码的意图是通过IFastString接口创建一个字符串,然后通过永久接口存储在文件中。以上代码的意图是通过IFastString接口创建一个字符串,然后通过永久接口存储在文件中。 • 实体对象最初是通过IFastString接口与客户联系起来的,然而最后是通过IPersistentObject接口delete的,删除操作会通过虚表找到正确的函数,功能上不会出错。然而客户(程序员)必须(在心里)记录下哪个指针与哪个对象联系在一起,每个对象只能调用一次Delete方法,而且一旦调用后,所有与之关联的接口指针都不再有效。程序员必须对这些指针的生命周期完全把握清楚。这对于以上简单的例子并不难,但是在实际的开发工作中,采用以上的方式给开发人员带来较大的负担。

  44. 解决方案: • 每个对象维护一个引用计数,当接口指针被复制的时候,计数增加;接口指针被销毁的时候,计数减少。 • Class IExtensibleObject{ Public: virtual void * Dynamic_cast(const char*pszType)=0; virtual void Delete()=0;} • 改进为: • Class IExtensibleObject{ Public: virtual void * Dynamic_cast(const char*pszType)=0; virtual void DuplicatePointer(void)=0; virtual void DestroyPointer(void)=0; }

  45. class FastString : public IFastString ,public IPersistentObject { int m_cPtrs;//引用指针数目 ...... public: FastString(const char* psz):m_cPtrs(0){}; //引用指针数目初始化为0; void DuplicatePointer(void) { ++m_cPtrs;} //增加引用计数 void DestroyPointer(void) { if(--m_cPtrs==0) delete this;} //减少引用计数,减到0时,删除对象自身. ......}; • CreateFastString 函数改为: IFastString *CreateFastString(const char*psz){ IFastString *pFS=new FastString(psz); If(pFS) pFS->DuplicatePointer(); return new pFS;} Dynamic_Cast函数改为:

  46. Void *FastString::Dynamic_Cast(const char *pszType){ Void* pv=0; If(strcmp(pszType,”IFastString”)==0) pv=static_cast<IFastString*>(this); else If(strcmp(pszType,”IPersistentObject”)==0) pv= static_cast<IPersistentObject*>(this); Else If(strcmp(pszType,”IExtensibleObject”)==0) pv= static_cast<IFastString*>(this); Return 0;//未支持接口的请求。 Pv->DuplicatePointer(); // 引用计数加1 Return pv; }

  47. 于是:客户遵守以下两条: • 1。当接口指针被复制时,调用DuplicatePointer • 2。当接口指针不再有用时调用DestroyPointer • void f(void){ IFastString *pfs=0; IPersistentObject *ppo=0; pfs=CreateFastString("asdf"); if(pfs) { ppo=(IPersistentObject*) pfs->Dynamic_Cast("IPersistentObject"); if(ppo) { ppo->Save("c:\\myfile"); ppo->DestroyPointer();} pfs->DestroyPointer();//每个指针各自负责自己的引用计数操作。 } } • 每个指针都被看作有独立生命周期的实体,客户无需把指针与对象联系起来,直观,易用。客户只需要遵守两条规则,让对象自己管理其生命周期。

  48. 以上我们对一个可重用的二进制组件对象的改进过程,实际上就是Microsoft公司在1988年到1993年间设计COM的过程。以上我们对一个可重用的二进制组件对象的改进过程,实际上就是Microsoft公司在1988年到1993年间设计COM的过程。

More Related