weitom1982

向各位技术前辈学习,学习再学习.
posts - 299, comments - 79, trackbacks - 0, articles - 0
  IT博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

在C++中使用接口(进阶)

Posted on 2006-03-21 16:18 高山流水 阅读(623) 评论(0)  编辑 收藏 引用 所属分类: 程序语言

 面向对象的语言诸如JAVA提供了Interface来实现接口,但C++却没有这样一个东西,尽管C++通过纯虚基类实现接口,譬如COM的C++实现就是通过纯虚基类实现的(当然MFC的COM实现用了嵌套类),但我们更愿意看到一个诸如Interface的东西。下面就介绍一种解决办法。

首先我们需要一些宏:
//
// Interfaces.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
是不是很像MFC消息映射那些宏啊,熟悉MFC的朋友一定不陌生。

现在我们可以像下面这样来实现我们的接口了:
//
// Foo.h
//

#include 
"BasicFoo.h"
#include 
"IBar.h"

class Foo : public BasicFoo, implements IBar
{
// Construction & Destruction
public:
   Foo(
int x) : BasicFoo(x)
   {
   }

   
~Foo();

// IBar implementation
public:
   
virtual int GetBarData() const
   {
      
// add your code here
   }

   
virtual void SetBarData(int nData)
   {
      
// add your code here
   }
};

怎么样,很简单吧,并不需要做很多的努力我们就可以在C++中使用接口了。然而,由于这并不是语言本身所直接支持的特性,所以我们需要遵循一些规则:
         a)   声明一个类的时候,如果你的类除了要从接口类继承外还要从另一个类继承(结构上的继承,即is a关系),则把这个类作为第一个基类,就像我们平时做的一样,譬如CFrameWnd从CWnd继承,CBitmapButton从CButton继承,CMyDialog从CDialong继承。当你要从MFC类派生的时候,这尤其重要,把他们声明为第一个基类以避免破坏MFC的RuntimeClass机制。
         b)   其他的基类紧跟其后,有多少就跟多少,如果你需要的话。譬如:class Foo : public BasicFoo, implements IBar, implements IOther, implements IWhatever, ...
         c)   接口类里面不要声明任何成员变量。接口类仅用于描述行为而不是数据。当你要作多重继承时,这样做可以避免数据成员被从同一个接口类多次继承。
         d)   接口类的所有成员函数定义为纯虚函数。这可以确保你的实现类来实现这些函数的全部,当然你也可以在抽象类实现部分函数,只要在你的派生类里实现剩下的函数。
         e)   不要从除了接口类的其他任何类派生你的接口类。DeclareBasedInterface()可以做到这个.普通类可以选择实现基接口还是派生的接口,后面一种意味着两者都要实现。
   f) 将一个指向实现接口的类的指针赋值给一个指向该接口类的指针是不需要强制类型转换的,但反过来将一个接口类的指针赋值给一个实现该接口的类的指针就需要一个显式的强制类型转换。事实上我们可能会使用多重继承,这样这些转换我们就不能使用老式的转换。不过使用运行时类型信息(使用/GR选项)和动态类型转换可以很好的工作当然也更安全。
   g) 此外dynamic_cast为你提供了一种查询一个对象或接口是否实现了一个指定的接口的途径。
   h) 你还要非常小心的避免不同接口函数的命名冲突。

如果你仔细观察DeclareInterface 和 DeclareBasedInterfaca宏你会发现有一个操作是必须的:每个接口类都有一个虚析构函数。你可能认为这不重要,但是如果没有这个就可能会导致一些问题,看看下面的例子:

DeclareInterface(IBar)
   
virtual  LPCTSTR GetName()  const   =   0 ;
   
virtual   void  SetName(LPCTSTR name)  =   0 ;
EndInterface

class  Foo : implements IBar
{
//  Internal data
private :
   
char *  m_pName;

//  Construction & Destruction
public :
   Foo()
   {
      m_pName 
=  NULL;
   }

   
~ Foo()
   {
      ReleaseName();
   }

//  Helpers
protected :
   
void  ReleaseName()
   {

      
if  (m_pName  !=  NULL)
         free(m_pName);
   }

//  IBar implementation
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;
      }
   }
};

就像你看到的一样,这里有一个类工厂,它根据BarType来创建一个IBar的实现,当你使用完以后你当然希望要delete该对象,你会像下面这样做:
int  main()
{
   IBar
*  pBar  =  BarFactory::CreateBar(Foo);

   pBar
-> SetName( " MyFooBar " );
   
//  Use pBar as much as you want,
   
//  

   
//  and then just delete it when it's no longer needed
   delete pBar;     //  Oops!
}

delete pBar 做了什么取决于该对象是否有一个虚析构函数。如果Foo没有一个虚析构函数,则只有IBar 的隐式的空析构函数被调用,Foo的析构函数不会被调用,这样就发生了内存泄露。接口类里虚析构函数的声明避免了这用状况,它确保每个实现接口的类都有一个虚析构函数。

当你使用DeclareInterfac的时候,记得使用EndInterface和它匹配。Interface 宏和 implements宏仅仅是代替了class和public,这看起来是多余的,但我认为它们更明确的表达了代码的意图。如果我这么写:class Foo : public IBar,你可能认为这只是一个简单的继承;但如果我这么写:class Foo: implements IBar,你就会看到它实际的价值和意图---这是对一个接口的实现,而不是简单的一次继承。

posted on 2005-09-24 19:36 christanxw 阅读(504) 评论(2)  编辑 收藏收藏至365Key 所属分类: C++/STL

评论

# re: 在C++中使用接口  回复  

部分有用,但是好像和JAVA的接口类型还是有差异吧,我对Java不熟,最近看jhotdraw发现好像java的interface关键字表明为接口,但是implement接口不一定真要实现接口里的每个方法。例如:
public interface Locator extends Storable, Serializable, Cloneable {
public Point locate(Figure owner);
}
public abstract class AbstractLocator
implements Locator, Storable, Cloneable {

protected AbstractLocator() {
}

public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
}

public void write(StorableOutput dw) {
}

public void read(StorableInput dr) throws IOException {
}
}
注意,接口里的locate在继承类里并没有实现;
因此,我觉得要用c++实现类似java的interface,interface里的函数应该是有默认实现体的虚函数,而将析构函数做为纯虚函数,如:
class Locator
{
public:
virtual ~Locator()=0;
virtual wxPoint locate(Figure* owner) {};
};
2005-11-30 22:08 | 快乐浪子

# re: 在C++中使用接口  回复  

采用这种方法在C++中使用接口很有新意。楼上所说的在java中“implement接口不一定真要实现接口里的每个方法”,是不完全正确的。在Java中Abstract class可以不用实现接口里的所有方法,但除此之外的java class,如果要implement 接口,必须实现接口中定义的每个方法,除非它的父类中已经实现了这些方法。
只有注册用户登录后才能发表评论。