一个巧妙的接口指针类开始创建的方式与一个巧妙的指针类是一样的:通过为一个类执行操作符-》。这个处理过程也可被称为委派。通过覆盖操作符->,我们可以做一个类模仿一个指针调用。例如:
void Draw() { CSmartInterface<idraw> SIDraw; CoCreateInstance(...,IID,_IDraw, (void**)&SIDraw) ; SIDraw->Draw() ; } |
在上面的代码中有几点需要注意。首先,一个模板类被用于执行巧妙的接口指针。这使得巧妙的指针接口类是个安全类型。第二,就象我们就要看到的,操作符-已经被超载去返回一个包括在CsmantInterface中的指针的地址。第三,即使SIDraw不是一个指针,我们也使用操作符->去调用Idraw接口中的成员。第四,我们不调用Release是因为在栈上已经创建了CxmartInterface,析构函数会自动调用Release。
下面是CsmartInterface头文件中重要的部分,所以的成员函数和操作符都在头文件中稍后的部分中定义了。
CsmartInterface包含着一个指向一个接口的指针。为了类型安全,它被定义为一个模板函数。CsmartInterface的实质是超载操作符->。
template <class i> class CSmartInterface { public: // Construction CSmartInterface(I* pI = NULL) ; // Copy Constructor CSmartInterface(const CSmartInterface<i>& rSI) ; // Destruction ~CSmartInterface() ; // Assignment from I* CSmartInterface& operator=(I* pI) ; // // Operators // // Conversion operator I*() ; // Deref I* operator->() ; // Address of I** operator&() ; // Equality BOOL operator==(I* pI) const; // Inequality BOOL operator!=(I* pI) const; // Negation BOOL operator!() const ; protected: I* m_pI ; }; |
所以,来自前一个例子的SIDraw->Draw()导致了对SIDraw.m_PI->Draw()的调用。SIDraw把Draw调用委派给m-PI指向的接口。这种方式的强大之处在于CsmartInterface
为了使CsmartInterface成为纯粹的C++指针的一个更为令人可信的模拟,处理操作符 ->还需要定义别的操作符。事实上,做一个巧妙的指针类的最困难的事情是确保所有用在指针上的操作符都已经定义并且有意义。例如:当我把下面的代码从if(PISimple==NULL)转变成if (SISimple == NULL)。。。。。。,我就必须为我的巧妙的指针类 定义操作符==。生成的代码编译没有错误,不过,它包含着一个错误因为它把NULL 与SISimple比较而不是和我已经扩展的SISimple.m_PI比较。在我定义了操作符==后,这个错误消失了。你喜爱的C++编程书的目录应该列出了所以你需要定义的操作符,从而充当一个方便的查询表。为了安全起见,我定义了我认为我不需要的操作符--这样如果我视图用他们就会得到一条错误信息。类似于C/C++用户月刊中的Robert Mashlan的"C++中被查过的指针 "的文章能真正帮助你理解巧妙的指针。超载绝大部分需要的操作符使很直接明白的。最有趣的是操作符=:
template <class i> inline CSmartInterface<i>& CSmartInterface<i>::operator=(I* pI) { if (m_pI != pI) //OPTIMIZE: Same pointer AddRef/Release not needed. { if (m_pI != NULL) m_pI->Release() ; //Smart Pointers don't use smart pointers :-) m_pI = pI ; if (m_pI != NULL) m_pI->AddRef() ; } return *this ; } |
操作符=会自动地为你添加和释放接口。如果CsmartInterface已经指向一个接口,它将会释放它并且添加新的接口。这个操作符=的定义允许下列操作:
void DrawThemAll() { CSmartInterface<idraw> SIDraw ; for (int i = 0 ; i < MAX ; i++ ) { SIDraw = pIDraw[i] ; SIDraw->Forward(x) ; } } |
上面的代码依靠CsmartInterface的析构函数去释放指针,方法是:
template <class i> inline CSmartInterface<i>::~CSmartInterface() { if (m_pI != NULL) { m_pI->Release(); } } |
我们使用恰当的构造函数可以更好地利用析构函数
template <class i> inline CSmartInterface<i>::CSmartInterface(I* pI /*=NULL*/) : m_pI(pI) { if (m_pI != NULL) { // AddRef if we are copying an existing interface pointer. m_pI->AddRef() ; } } |
现在我们的例子可以改为:
void DrawThemAll() { for (int i = 0 ; i < MAX ; i++ ) { CSmartInterface<idraw> SIDraw(pIDraw[i]) ; SIDraw->Forward(x) ; } } |
上面的代码运行过一个Idraw接口指针的列表,添加他们,使用他们和释放他们。
关于这一点你还可以作的更多。DonBox在他的专题里把这一点阐述得更为深入。他定义了他的CsmartInterface的等价物去接受接口的ID和类型。然后他定义了一个构造函数,一旦有了来自不同接口的赋值,这个接口函数将调用Query Interface:
CSmartInterface(IUnknown* pI) : m_pI(NULL) { if (pI != NULL) pI->QueryInterface(*piid, (void**)&m_pI) ; } |
上面的构造函数允许我们把例子改为:
void DrawThemAll() { for (int i = 0 ; i <<IDraw, &IID_Draw> MAX ; i++ ) { CSmartInterface SIDraw(pIUnknown[i]) ; SIDraw->Forward(x) ; } } |
这个例子开始表现这种技术的强大之处,上面的代码类似于:
void DrawThemAll() { IDraw* pIDraw ; for (int i = 0 ; i < MAX ; i++ ) { pIUnknown[i]->QueryInterface(IID_Draw, (void**)&pIDraw) ; pIDraw->Forward(x) ; pIDraw->Release() ; } } |
操作符=也能用同样的方式扩展,所以类似于SIDraw=PIUnknown;这样的赋值将会调用QueryInterface。我并不热衷于把执行藏在看起来无害的操作符后 ,尽管我不得不说用这种方式超载操作符=是非常令人信服的方式。Visual Basic 4.0版本在把一个组件对象模型分配给另一个时就要调用QueryInterface。