技术开发 频道

C++接口概念“方法学”

IT168 专稿简介
接口(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 是一个”的关系),例如:CFrameWnd继承自CWnd,CBitmapButton继承自CButton,xxDialog继承自CDialog。尤其当在使用MFC时,这点非常重要,以避免破坏MFC的运行时类机制。
² 为实现接口使用额外的基类,有多少个接口,就用多少个基类。例如:lass Foo : public BasicFoo, implements IBar, implements IOther, implements IWhatever
² 不要在接口中声明任何变量。接口是用来表示行为,而不是数据,除此以外,这样做还可以在使用多重继承并继承自同一个接口不止一次时,避免某些错误。
² 在接口中把所有的成员函数声明为纯虚函数(即带上“=0”)。这可确保声明实现接口的每个实例化的类都实现其自已的函数;也可在抽象类中部分实现一个接口,只要在继承来的类中实现了余下的函数。另外,接口不提供“基本”实现,因为必须保证每个得到接口指针的调用者,都可以调用它的任意成员;把所有的接口成员声明为纯虚,可在编译期间强制应用这条规则。
² 接口只能从接口继承,而从其他任何类型继承都不行,可使用DeclareBasedInterface()来达到此目的。正常的类能选择实现基接口或继承来的接口,而后者也意味着实现了前两者。

把一个实现了某些接口的类的指针,赋值给这些接口的指针,并不需要进行转换,因为实际上是在把它转换为一个基类;但反过来,把一个接口指针,赋给一个实现它的类的指针,就需要显式转换了,因为是在把它转换为一个继承类。由于我们实际上是在使用多重继承——也可把它看作单重继承加上接口实现,且它们可能需要不同的内存值,所以“老式”的转换方法就行不通了;然而,打开RTTI(Run-Time Type Information运行时类型信息)/GR编译选项,并使用dynamic_cast,就完全没有问题了,且在任何情况下都是安全的。进一步来说,使用dynamic_cast还可以查询任意对象及接口,是否实现了某个特定的接口。

另外,还必须小心避免在不同接口中的函数名冲突。
0
相关文章