尽管DBPtr<Tuple>到Tuple*再到TupleAccessor的转换就可以匹配merge的参数,但编译器禁止这么做.
解决方法是使用普通成员函数进行显式转换以代替隐式转换操作符,像这样:
class DBPtr {
public:
...
T* toPrimary() { return pointee; }
...
};
6. Smart Pointers和"与继承有关的"类型转换
两个类之间有继承关系,但以这两个类为参数具现化的类模板却没有继承关系,由于智能指针是类模板,因此智能指针的包装会屏蔽内置指针的继承关系,例如对于以下继承层次:
class MusicProduct {
public:
MusicProduct(const string& title);
virtual void play() const = 0;
virtual void displayTitle() const = 0;
...
};
class Cassette: public MusicProduct {
public:
Cassette(const string& title);
virtual void play() const;
virtual void displayTitle() const;
...
};
class CD: public MusicProduct {
public:
CD(const string& title);
virtual void play() const;
virtual void displayTitle() const;
...
}
void displayAndPlay(const MusicProduct* pmp, int numTimes)
{
for (int i = 1; i <= numTimes; ++i) {
pmp->displayTitle();
pmp->play();
}
整个继承体系像这样:
由于各个类的继承关系,可以利用指针的多态实现面向对象编程,像这样:
Cassette *funMusic = new Cassette("Alapalooza");
CD *nightmareMusic = new CD("Disco Hits of the 70s");
displayAndPlay(funMusic, 10);
displayAndPlay(nightmareMusic, 0);
但当指针经过封装成为智能指针之后,正如开始所说,以下代码将无法通过编译:
void displayAndPlay(const SmartPtr<MusicProduct>& pmp,int numTimes);
SmartPtr<Cassette> funMusic(new Cassette("Alapalooza"));
SmartPtr<CD> nightmareMusic(new CD("Disco Hits of the 70s"));
displayAndPlay(funMusic, 10); // 错误!
displayAndPlay(nightmareMusic, 0); // 错误!
这是由于MusicProduct,Cassette,CD之间有继承关系,但智能指针SmartPtr<MusicProduct>,SmartPtr<Cassette>,SmartPtr<CD>之间却没有内在的继承关系.
最直接的解决方法是为每一个智能指针类定义一个隐式类型转换操作符,像这样:
class SmartPtr<Cassette> {
public:
operator SmartPtr<MusicProduct>()
{ return SmartPtr<MusicProduct>(pointee); }
...
private:
Cassette *pointee;
};
class SmartPtr<CD> {
public:
operator SmartPtr<MusicProduct>()
{ return SmartPtr<MusicProduct>(pointee); }
...
private:
CD *pointee;
};
这种方法可以解决类型转换的问题,但是却治标不治本:一方面,必须为每一个智能指针实例定义隐式类型转换操作符,这无疑与模板的初衷背道相驰;另一方面,类的继承层次可能很庞大,采用以上方式,继承层次的最底层类的负担将会非常大——必须为对象直接或间接继承的每一个基类提供隐式类型转换操作符.
"将nonvirtual member function声明为templates"是C++后来接入的一个性质,使用它可以从根本上解决饮食类型转换的问题,像这样:
template<class T>
class SmartPtr {
public:
SmartPtr(T* realPtr = 0);
T* operator->() const;
T& operator*() const;
template<class newType> // 模板成员函数
operator SmartPtr<newType>()
{
return SmartPtr<newType>(pointee);
}
...
};
这个成员函数模板将智能指针之间的隐式类型转换交由底层内置指针来完成,保证了指针转换的"原生态":如果底层指针能够转换,那么包装后的智能指针也能够进行转换.唯一的缺点是它是通过指针之间的隐式类型转换来实现指针的多态,也就是说,它实际上并不能区分对象之间的继承层次,假如扩充MusicProduct的继承体系,加上一个新的CasSingle class,像这样:
那么对于以下代码:
template<class T>
class SmartPtr { ... };
void displayAndPlay(const SmartPtr<MusicProduct>& pmp,
int howMany);
void displayAndPlay(const SmartPtr<Cassette>& pc,
int howMany);
SmartPtr<CasSingle> dumbMusic(new CasSingle("Achy Breaky Heart"));
displayAndPlay(dumbMusic, 1);//错误,隐式类型转换函数的调用具有二义性
正如之前所言,使用隐式类型转换操作符实现的指针多态并不能区分对象的继承层次,也就是说将SmartPtr<CasSingle>转为SmartPtr<Cassette>&和转为SmartPtr<MusicProduct>&具有同样的优先级,因此造成二义性.而内置指针却能做到这一点,它优先将CasSingle绑定到Cassette&,因为CaSingle直接继承自Cassette.此外,以上策略还有移植性不高的缺点:有些编译器可能并不支持member templates.
7. Smart Pointers与const
对于内置指针,const修饰的含义因其位置而不同:
CD goodCD("Flood");
const CD *p; // p 是一个non-const 指针,指向 const CD 对象
CD * const p = &goodCD; // p 是一个const 指针,指向non-const CD 对象;因为 p 是const,它必须在定义时就被初始化
const CD * const p = &goodCD; // p 是一个const 指针,指向一个 const CD 对象
但对于智能指针,只有一个地方可以放置const,因此cosnt只能施行于指针之上,而不能施行于指针所指对象之上:
const SmartPtr<CD> p=&goodCD;
要是const修饰所值对象很简单,像这样:
SmartPtr<const CD> p=&goodCD;
由此方法可以实现和内置指针相同的四种指针:
SmartPtr<CD> p; // non-const 对象, non-const 指针
SmartPtr<const CD> p; // const 对象,non-const 指针
const SmartPtr<CD> p = &goodCD; // non-const 对象,const 指针
const SmartPtr<const CD> p = &goodCD; // const 对象,const 指针
但这种方法仍有缺陷,正如经由模板包装之后,有继承关系的两个类完全没有关系一样,经由智能指针模板包装后的const和non-const对象完全不同,像这样看起来理所当然的代码通不过编译:
SmartPtr<CD> pCD = new CD("Famous Movie Themes");
SmartPtr<const CD> pConstCD = pCD;
使用之前的隐式类型转换技术可以顺带解决这个问题,但又有所区别:const与non-const的转换是单向的,即可以对const指针做的事也可以对non-const指针进行,但可以对non-const指针做的事未必可以对const指针进行.这与public继承类似,利用这种性质,令每一个smart pointer-to-T-class public继承一个对应的smart pointer-to-const-T class:
template<class T> // 指向const 对象的
class SmartPtrToConst {
protected:
union {
const T* constPointee; // 提供给SmartPtrToConst 访问
T* pointee; // 提供给SmartPtr 访问
};
};
template<class T>
class SmartPtr: public SmartPtrToConst<T> {
public:
template<class constType>
operator SmartPtrToConst<constType>();
... //没有额外数据成员
};
SmartPtrToConst使用了union,这样constPointee和pointee共享同一块内存SmartPtrToConst使用constPointee,SmartPtr使用pointee.
现在,使用SmartPtrToConst和SmartPtr分别代表指向const和non-const对象的智能指针,以下代码可以通过编译:
SmartPtr<CD> pCD = new CD("Famous Movie Themes");
SmartPtrToConst<CD> pConstCD = pCD;
8. 从2-7的讨论可以看出,智能指针功能强大,但索要付出的代价也很高,此外,智能指针无论如何也不能完全替代内置指针.当然,尽管内置指针在实现和维护方面需要大量技巧,但与其强大的功能相比在多数情况下还是值得的.