面向对象的语言诸如JAVA提供了Interface来实现接口,但C++却没有这样一个东西,尽管C++通过纯虚基类实现接口,譬如COM的C++实现就是通过纯虚基类实现的(当然MFC的COM实现用了嵌套类),但我们更愿意看到一个诸如Interface的东西。下面就介绍一种解决办法。
首先我们需要一些宏:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// // 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 |
有了这些宏,我们就可以这样定义我们的接口了:
1 2 3 4 5 6 7 8 |
// // IBar.h // DeclareInterface(IBar) virtual int GetBarData() const = 0; virtual void SetBarData(int nData) = 0; EndInterface |
是不是很像MFC消息映射那些宏啊,熟悉MFC的朋友一定不陌生。
现在我们可以像下面这样来实现我们的接口了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// // 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, implementsIWhatever, …
c) 接口类里面不要声明任何成员变量。接口类仅用于描述行为而不是数据。当你要作多重继承时,这样做可以避免数据成员被从同一个接口类多次继承。
d) 接口类的所有成员函数定义为纯虚函数。这可以确保你的实现类来实现这些函数的全部,当然你也可以在抽象类实现部分函数,只要在你的派生类里实现剩下的函数。
e) 不要从除了接口类的其他任何类派生你的接口类。DeclareBasedInterface()可以做到这个.普通类可以选择实现基接口还是派生的接口,后面一种意味着两者都要实现。
f) 将一个指向实现接口的类的指针赋值给一个指向该接口类的指针是不需要强制类型转换的,但反过来将一个接口类的指针赋值给一个实现该接口的类的指针就需要一个显式的强制类型转换。事实上我们可能会使用多重继承,这样这些转换我们就不能使用老式的转换。不过使用运行时类型信息(使用/GR选项)和动态类型转换可以很好的工作当然也更安全。
g) 此外dynamic_cast为你提供了一种查询一个对象或接口是否实现了一个指定的接口的途径。
h) 你还要非常小心的避免不同接口函数的命名冲突。
如果你仔细观察DeclareInterface 和 DeclareBasedInterfaca宏你会发现有一个操作是必须的:每个接口类都有一个虚析构函数。你可能认为这不重要,但是如果没有这个就可能会导致一些问题,看看下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
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该对象,你会像下面这样做:
1 2 3 4 5 6 7 8 9 10 11 |
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,你就会看到它实际的价值和意图—这是对一个接口的实现,而不是简单的一次继承。
发表评论
要发表评论,您必须先登录。