C++接口(转载)

来源:互联网 发布:mac怎么用u盘做启动盘 编辑:程序博客网 时间:2024/06/04 18:29

__declspec(novtable) 在C++中接口中广泛应用. 不容易看到它是因为在很多地方它都被定义成为了宏. 比如说ATL活动模板库中的ATL_NO_VTABLE, 其实就是__declspec(novtable).

 __declspec(novtable) 就是让类不要有虚函数表以及对虚函数表的初始化代码, 这样可以节省运行时间和空间. 但是这个类一定不允许生成实例, 因为没有虚函数表, 就无法对虚函数进行调用. 因此, __declspec(novtable)一般是应用于接口(其实就是包含纯虚函数的类), 因为接口包含的都是纯虚函数, 不可能生成实例. 我们把 __declspec(novtable)应用到接口类中, 这些接口类就不用包含虚函数表和初始化虚函数表的代码了. 它的派生类会自己包含自己的虚函数表和初始化代码.

 

接口是一个没有被实现的特殊的类,它是一系列操作的集合,我们可以把它看作是与其他对象通讯的协议。C++中没有提供类似interface这样的关键 字来定义接口,但是Mircrosoft c++中提供了__declspec(novtable)来修饰一个类,来表示该类没有虚函数表,也就是虚函数都是纯虚的。所以利用它我们依然可以定义一 个接口。代码例子如下:

 

#include <IOSTREAM>

using namespace std;

#define interface class __declspec(novtable)

interface ICodec

{

public:

    virtual bool Decode(char * lpDataSrc,unsigned int nSrcLen,char * lpDataDst,unsigned int *pnDstLen);

    virtual bool Encode(char * lpDataSrc,unsigned int nSrcLen,char * lpDataDst,unsigned int *pnDstLen);

};

class CCodec : public ICodec

{

public:

    virtual bool Decode(char * lpDataSrc,unsigned int nSrcLen,char * lpDataDst,unsigned int *pnDstLen)

     {

         cout << "解码..." << endl;

        return true;

     }

    virtual bool Encode(char * lpDataSrc,unsigned int nSrcLen,char * lpDataDst,unsigned int *pnDstLen)

     {

         cout << "编码..." << endl;

        return true;

     }

};

int main(int argc, char* argv[])

{

     ICodec * pCodec = new CCodec();

     pCodec->Decode(NULL,0,NULL,NULL);

     pCodec->Encode(NULL,0,NULL,NULL);

     delete (CCodec*)pCodec;

    return 0;

}

 

 

上面的ICodec接口等价于下面的定义:

class ICodec

{

public:

    virtual bool Decode(char * lpDataSrc,unsigned int nSrcLen,char * lpDataDst,unsigned int *pnDstLen)=0;

    virtual bool Encode(char * lpDataSrc,unsigned int nSrcLen,char * lpDataDst,unsigned int *pnDstLen)=0;

};

 

 

接口(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;    //啊呀!
}

 

文章转自:http://blog.163.com/fenglin9999@126/blog/static/475672482009111210243997/

原创粉丝点击