条款18:重载运算符不应当扭曲其语义
_com_ptr_t在各种比较运算符上都调用了_CompareUnknown这么一个函数,我们看看他到底做了什么:
template<typename _InterfaceType> bool operator<(_InterfaceType* p)
{
return _CompareUnknown(p) < 0;
}
template<typename _InterfaceType> bool operator==(_InterfaceType* p)
{
return _CompareUnknown(p) == 0;
}
template<typename _InterfacePtr> int _CompareUnknown(_InterfacePtr p)
{
IUnknown* pu1, *pu2;
if (m_pInterface != NULL) {
HRESULT hr =m_pInterface->QueryInterface(
__uuidof(IUnknown), reinterpret_cast<void**>(&pu1)
);
if (FAILED(hr)) {
_com_issue_error(hr);
pu1 = NULL;
}
else {
pu1->Release();
}
}
else {
pu1 = NULL;
}
if (p != NULL) {
HRESULT hr = p->QueryInterface(
__uuidof(IUnknown), reinterpret_cast<void**>(&pu2)
);
if (FAILED(hr)) {
_com_issue_error(hr);
pu2 = NULL;
}
else {
pu2->Release();
}
}
else {
pu2 = NULL;
}
return pu1 - pu2;
}
貌似他的做法确实有点复杂,但是稍加解释你就会明白:_CompareUnknown对两个接口分别查询IUnknonw接口,之后判断查询到的接口是否相同。你可能会惊讶!智能指针的比较操作符,并非比较的指针本生,而是比较的两个指针是否指向的同一个组件!
这种做法确实让人难以捉摸,但回顾一下他的历史原因,你可能会明白。在VB中普通的COM变量映射到VC中成了指向COM的智能指针。而VB中对于COM变量的判断则应该是判断其是否指向同一组件。因此_com_ptr_t也就这么做了,但语义上确实与普通的指针产生了很大的差异。因此有时候是同他在同接口指针进行比较之时,会出现令人费解的情况。如下所示:
ICalculatorPtr spCalculator(CLSID_CALCULATOR);
ICOMDebuggerPtr spCOMDebugger = spCalculator;
IUnknown *pIUnknown = spCalculator;
ICalculator *pCalculator = spCalculator;
ICOMDebugger *pCOMDebugger = spCOMDebugger;
if (pCOMDebugger == pIUnknown)
{
DoSomething();//不会执行这句,因为两个指针地址不同。
}
if (pCOMDebugger == spCalculator)
{
DoSomething();//会执行这句,因为两个接口指针所指为同一个对象。
}
再看看CComPtr的比较函数,可能他会是你希望的样子。
bool operator!=(_In_opt_ T* pT) const
{
return !operator==(pT);
}
bool operator==(_In_opt_ T* pT) const throw()
{
return p == pT;
}
bool operator<(_In_opt_ T* pT) const throw()
{
return p < pT;
}
嗯~ 但是你却找不到他的>=、>、<=这类函数。他们哪里去了?回顾一下ATL的历史,你似乎意识到了这真是ATL的简单之处。简单,刚刚好,就已经可以了。至于大于等于和小于等于这些关系,完全可以通过<与==组合出来。而只提供一个<更意味着CComPtr遵循严格弱序这种公约。他可以使得你的智能指针有放入Set和Map这类关联容器中去的可能性(这些关联容器比较时,使用以严格弱序为基础的等价概念,而并非等值)。
而_com_ptr_t就不那么幸运了。为了兼容VB和其他解释型语言的特点。其代码不得不附加上每一种比较函数的单目版本,但这还不够,很多情况下还需要一个双目版本用于智能指针与非智能指针之间的比较:
template<typename _Interface>
bool operator<=(int null, _com_ptr_t<_Interface>& p)
{
if (null != 0) {
_com_issue_error(E_POINTER);
}
return p >= NULL;
}
template<typename _Interface, typename _InterfacePtr>
bool operator<=(_Interface* i, _com_ptr_t<_InterfacePtr>& p)
{
return p >= i;
}
你现在知道了。如果不是出于特殊原因,不要使用使用比较运算符来判断智能指针是否指向统一组件。这种扭曲运算符语义的做法会让你的代码难以琢磨。而当你需要这一功能的时候,应给给出一个相应功能的函数。这样做会让你的代码更加的清晰易懂。智能指针自身也会变得简洁:
// Compare two objects for equivalence
bool IsEqualObject(_In_opt_ IUnknown* pOther) throw()
{
if (p == NULL && pOther == NULL)
return true; // They are both NULL objects
if (p == NULL || pOther == NULL)
return false; // One is NULL the other is not
CComPtr<IUnknown> punk1;
CComPtr<IUnknown> punk2;
p->QueryInterface(__uuidof(IUnknown), (void**)&punk1);
pOther->QueryInterface(__uuidof(IUnknown), (void**)&punk2);
return punk1 == punk2;
}