[原创文章欢迎转载,但请保留作者信息]
Justin 于 2010-04-26
看完以后觉得讲的其实是如何用模板函数来让拷贝构造函数和赋值运算符接受所有可能的输入。
首先对于内建类型的对象,他们作为函数参数时,参数的类型转换是自动发生的。比如说派生类对象的指针,如果需要,会被转换为其父类对象的指针。
而对于智能指针,要完成类似的转换需要编写额外的代码才能实现。
如书中例子所示,如果类Base是类Derived的父类,对于下面的一个模板类:
template <typename T>
class SmartPointer {
public:
explicit SmartPointer(T * pointer);
//..
}
经管这两个智能指针指向的对象是有继承关系的,SmartPointer<Base>和SmartPointer<Derived>对于编译器来说完全没有联系。
于是,如果一个函数声明了接受一个SmartPointer<Base>类型的参数,就无法给它传进一个SmartPointer<Derived>类型的指针了。
为了能够让智能指针对象也具备一般指针的隐式转换能力,首先可以做的工作是改造智能指针的构造函数,让对象生成的时候可以依照具体情况“变态”成实际需要的对象类型。好吧,那我们现在就开始做……
打住!有两个问题需要考虑:第一是我们怎么知道智能指针构造函数的输入参数pointer是什么类型?又怎么知道真正需要的目标类型是什么?如果不知道我们怎么写这个构造函数?第二是就算我们知道了输入的类型和生成的类型,有多少种这样的搭配是SmartPointer需要接受的?如果有100对不同的类型搭配,我们就要写100种构造函数吗?
解决这些问题的办法:用“模板化”的构造函数。(member function templates)
template <typename T>
class SmartPointer {
public:
template <typename U>
SmartPointer(const SmartPointer<U>& input) : myPointer( input.get() )
{//..
}
T* get() const { return myPointer; }
//..
private:
T* myPointer;
};
上面的方案个人感觉很绕,不过确实也解决了问题。
大概分析一下:
- 其中的构造函数是一个模板函数,接受参数为U类型的对象,构造出指向T的指针。
- 但是很明显我们不需要一个能够接受任意类型再生成任意类型对象的构造函数。这样会天下大乱的。于是就有了get()对myPointer的初始化:构造函数的输入参数input所持有的指针是可以用get()来得到的,然后有这个“get”来的指针去初始化目标对象所持有的指针,到了这一步,就和一般指针的隐式转换没有差别了:
- 如果可以转换,智能指针SmartPointer<U>就顺利转换为SmartPointer<T>;
- 如果不能转换,构造函数就失败。
- 这里的构造函数没有explicit修饰,因此类型的转换是隐式的。如果真有这个必要,就加上explicit,这样就需要显式地声明需要进行类型转换。
- 当一个类没有定义自己的构造函数时(包括赋值构造函数和拷贝构造函数),编译器会“帮你”创建默认的构造函数。编译器不会理睬“模板化”的构造函数,如果一个类中只有“模板化”的构造函数,它还是会继续生产默认的构造函数。因此,如果不想让编译器做多余的事,就有必要自己写好非模板化的构造函数,以此让编译器“闭嘴”@#¥%