c++中接口的实现


C++中接口的实现 简介 接口(Interface),作为一种比类更强大的语言特性,已出现在了 Java、C#及其他语言中,但 C++中却没有。本文中将要演示的,是一种 C++接口概念“方法学”上的实现;且从 Visual Studio.NET 2002 开始,微软也以一种扩展的方法来走这同一条路,其允许编译器来实现接口中的大多数特性,当 然了,C++托管扩展也支持 .NET 接口的定义与实现。不管怎样,在这些实现机制之间,还是有一些微 妙差别的,都需要你给予充分的重视。 一个接口在没有特定实现之前,其描述了类的行为或功能,代表了提供者与使用者都必须遵守的 约定,它定义各个实现者的所需完成的功能,而不管它们怎样具体去做。 第一个版本 首先,在头文件中定义了一些宏,你可在程序的预编译头文件中包含它: // // CppInterfaces.h // #define Interface class #define DeclareInterface(name) Interface name { \ public: \ virtual ~name() {} #define DeclareBasedInterface(name, base) class name : public base { \ public: \ virtual ~name() {} #define EndInterface }; #define implements public 使用这些宏,能以下面这种方式声明一个接口: // // IBar.h // DeclareInterface(IBar) virtual int GetBarData() const = 0; virtual void SetBarData(int nData) = 0; EndInterface 接下来,可像下面这样声明一个实现了这个接口的类: // // Foo.h // #include "BasicFoo.h" #include "IBar.h" class Foo : public BasicFoo, implements IBar { //构造及析构函数 public: Foo(int x) : BasicFoo(x) { } ~Foo(); // IBar 的实现 public: virtual int GetBarData() const { //函数体 } virtual void SetBarData(int nData) { //函数体 } }; 很简单吧,无须太费力,现在就能在 C++中使用接口了,但是,因为它们不是直接被语言支持的, 所以要遵循以下这些在编译时不能自动应用的规则,毕竟,在所有编译器的“眼中”,这些都是多重继承和 抽象基类。 ² 当声明一个类时,要使用基类来搭建“结构性”的继承(成为“is a 是一个 ”的关系),例如:CFrameW nd 继承自 CWnd,CBitmapButton 继承自 CButton,xxDialog 继承自 CDialog。尤其当在使用 MFC 时,这点非常重要,以避免破坏 MFC 的运行时类机制。 ² 为实现接口使用额外的基类,有多少个接口,就用多少个基类。例如:lass Foo : public BasicFoo, i mplements IBar, implements IOther, implements IWhatever ² 不要在接口中声明任何变量。接口是用来表示行为,而不是数据,除此以外,这样做还可以在使用多重 继承并继承自同一个接口不止一次时,避免某些错误。 ² 在接口中把所有的成员函数声明为纯虚函数(即带上“=0”)。这可确保声明实现接口的每个实例化的 类都实现其自已的函数;也可在抽象类中部分实现一个接口,只要在继承来的类中实现了余下的函 数。另外,接口不提供“基本”实现,因为必须保证每个得到接口指针的调用者,都可以调用它的任 意成员;把所有的接口成员声明为纯虚,可在编译期间强制应用这条规则。 ² 接口只能从接口继承,而从其他任何类型继承都不行,可使用 DeclareBasedInterface()来达到此目 的。正常的类能选择实现基接口或继承来的接口,而后者也意味着实现了前两者。 把一个实现了某些接口的类的指针,赋值给这些接口的指针,并不需要进行转换,因为实际上是 在把它转换为一个基类;但反过来,把一个接口指针,赋给一个实现它的类的指针,就需要显式转换了, 因为是在把它转换为一个继承类。由于我们实际上是在使用多重继承——也可把它看作单重继承加上接口 实现,且它们可能需要不同的内存值,所以“老式”的转换方法就行不通了;然而,打开 RTTI(Run-Tim e Type Information 运行时类型信息) /GR 编译选项,并使用 dynamic_cast,就完全没有问题了, 且在任何情况下都是安全的。进一步来说,使用 dynamic_cast 还可以查询任意对象及接口,是否实现 了某个特定的接口。 另外,还必须小心避免在不同接口中的函数名冲突。 进一步评估 也许在你看了上述文字之后,会有一些想法:为什么要为宏费心呢?而它们并没有用到什么强制 性的规则,也没有提高老式#define begin {、#define end }语法的可读性啊。 但如果你仔细观察 DeclareInterface 和 DeclareBasedInterface 宏,你会注意到还是有一些强 制性规则存在的:实现了一个接口的类都有一个虚拟析构函数;也许你认为它并不重要,但是如果缺少虚 拟析构函数,会导致某些问题,请看下面的代码: DeclareInterface(IBar) virtual LPCTSTR GetName() const = 0; virtual void SetName(LPCTSTR name) = 0; EndInterface class Foo : implements IBar { //内部数据 private: char* m_pName; //构造与析构函数 public: Foo() { m_pName = NULL; } ~Foo() { ReleaseName(); } protected: void ReleaseName() { if (m_pName != NULL) free(m_pName); } // IBar 的实现 public: virtual const char* GetName() const { return m_pName } virtual void SetName(const char* name) { ReleaseName(); m_pName = _strdup(name); } }; class BarFactory { public: enum BarType {Faa, Fee, Fii, Foo, Fuu}; static IBar CreateNewBar(BarType barType) { switch (barType) { default: case Faa: return new Faa; case Fee: return new Fee; case Fii: return new Fii; case Foo: return new Foo; case Fuu: return new Fuu; } } }; 如上所示,这里有一个类工厂( factory),可以基于 BarType 参数,请求它来创建 IBar 的某个 实现;在用完之后,往往会想到删除这个对象,目前为止,一切都正常,但如果用在某些程序的 main 函 数中呢: int main() { IBar* pBar = BarFactory::CreateBar(Foo); pBar->SetName("MyFooBar"); //尽可能地多使用 pBar // ... //在不需要时删除它 delete pBar; //啊呀! } 在执行到 delete pBar 这一行时会发生什么,完全依赖于对象的类是否有一个虚拟析构函数。如 果 Foo 没有一个虚拟析构函数,编译器只会生成一个对 IBar 隐式空析构函数的调用,而 Foo 的构析函 数并没有被调用,因此会造成内存漏泄。在接口声明宏中的虚拟析构函数就是为了要防止这种情况发生的, 它们保证了每个实现了一个接口的类,都会有一个虚拟析构函数。 既然我们使用了 DeclareInterface,那么用 EndInterface 来进行配对还是说得过去的,至少好 过用一个花括号来匹配吧。至于 Interface 与 implements,它们各自会解析为 class 与 public,使用 它们看上去似乎有点多余,但它们能更好地表达代码所代表的意图,比如说从 Foo : public IBar 中,就 只能看出继承关系,如果写成 Foo implements IBar,就可看出实际的用意了。 Visual Studio.NET 2002 中对 C++接口的支持 微软从 Visual Studio.NET 2002 开始,引入了一种新的关键字:__interface,它是一种针对 C ++编译器的微软扩展,在相应文档中,对一个 Visual C++接口进行了如下说明: ² 能从零个或多个基接口继承。 ² 不能继承自一个基类。 ² 只能包含公有及纯虚方法。 ² 不能包含构造函数、析构函数、操作符函数。 ² 不能包含静态方法。 ² 不能包含数据成员;但允许有属性。 文档还注明:“一个 C++类或结构可用以上规则来实现,但必须加上__interface。”因此,如果不 在意可移植性的话,是否可用__interface 这个扩展让编译器来完成剩下的工作呢?错了。 还记得虚拟析构函数吗?__interface 并不会为实现一个类而添加虚拟析构函数,如果你仔细看过 文档,就会发现它们实际上是由 COM 接口实现的,所以你从不会用到 delete,因为它们是引用计数的 对象,当计数为零时,会对自身调用 delete。 那么在 DeclareInterface 宏定义中使用__interface,这不就两全其美了吗?再看一下前面的规则: “不能包含构造函数、析构函数及操作符函数”。这可能让人觉得有点不可能,甚至会想到为什么要在 CO M 接口中使用__interface 呢,况且也不适合这里所说到的通用接口啊。那么,请再往下看。 两全其美的方法 这儿有一个非常简单的解决方案:接口的名字实际上用于声明一个类,而这个类包含了一个虚拟 析构函数且继承自包含了必要方法的一个__interface。现在宏定义如下: // // CppInterfaces2.h // #define Interface class #define implements public #define DeclareInterface(name) __interface actual_##name { #define DeclareBasedInterface(name, base) __interface actual_##name \ : public actual_##base { #define EndInterface(name) }; \ Interface name : public actual_##name { \ public: \ virtual ~name() {} \ }; 使用上面定义的宏来声明一个接口: // // IBar2.h // DeclareInterface(IBar) int GetBarData() const; void SetBarData(int nData); EndInterface(IBar) 也许你已注意到了,新的宏定义用到了两次接口名( IBar),第一次是 DeclareInterface(),而 第二次是 EndInterface()。另一方面,倘若你不怎么在意移植性问题,那么这个宏比微软编译器所提供 的扩展有利得多,因为接口(仅指提供了实现类的纯虚方法、虚拟析构函数且没有数据成员的接口)的方 方面面,都自动完成了,甚至不必显式声明接口方法为 virtual 或纯虚( =0)——虽然声明了也没什么 关系。 发表于 @ 2008 年 04 月 05 日 10:25:00|评论(loading... )|编辑|收藏
还剩6页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 6 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

neodoragon

贡献于2013-06-01

下载需要 6 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf