现在的位置: 首页 > 综合 > 正文

C++中接口的实现

2013年06月02日 ⁄ 综合 ⁄ 共 5353字 ⁄ 字号 评论关闭

简介
         接口(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还可以查询任意对象及接口,是否实现了某个特定的接口。

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

         进一步评估
         也许在你看了上述文字之后,会有一些想法:为什么要为宏费心呢?而它们并没有用到什么强制性的规则,也没有提高老式#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,这不就两全其美了吗?再看一下前面的规则:“不能包含构造函数、析构函数及操作符函数”。这可能让人觉得有点不可能,甚至会想到为什么要在COM接口中使用__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)——虽然声明了也没什么关系。
文章出处:http://www.diybl.com/course/3_program/c++/cppjs/200847/108557_2.html
文章出处:http://www.diybl.com/course/3_program/c++/cppjs/200847/108557.html

【上篇】
【下篇】

抱歉!评论已关闭.