Member template 成员模板
template <class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair()
: first(T1()), second(T2()) {}
pair(const T1& a, const T2& b)
: first(a), second(b) {}
// 成员模板
template <class U1, class U2>
pair(const pair<U1, U2>& p)
: first(p.first), second(p.second) {}
}
成员模板的语法如上
这一块是模板里面的成员, 而这个成员本身又是一个模板, 我们就把这一部分叫作成员模板
外头的模板是一个允许变化的东西, 变化的什么呢 ? T1 和 T2.
而在这种变化之下, 也就是 T1 和 T2 被确定下来后, 里面的这一小段又允许变化, 变化什么呢, U1 和 U2.
这个有点抽象, 下面我们来看具体的例子
这里设计了 4 个 class, 它们构造如图右上角所示
4 个 class 代码如下 :
class Base1 { };
class Derived1:public Base1 { };
class Base2 { };
class Derived2:public Base2 { };
4 个 class 里面什么都不用做, 我只要它们的继承关系
这里我拿 T1 和 T2 制造一个 p, T1 和 T2 实际上指的是鲫鱼和麻雀, 我把鲫鱼和麻雀做成一对. 下面一行是指我把鸟类和鱼类做成一对.
再来看下面这个
我可不可以把鲫鱼和麻雀的这一对, 当成初值, 塞给鱼类和鸟类的这一对里 ? 或者反过来可不可以 ?
也就是左下角的这句话, 把鲫鱼和麻雀构成的 pair, 放进 (拷贝到) 一个由鱼类和鸟类构成的 pair 中, 可以吗 ? 反之, 可以吗 ?
当然可以了, 想一想生活经验. 反之则不行.
由于要满足上面的问题, 这种可以还是不可以的问题, 所以设计 pair 的人允许你放任意的 T1 和 T2, 并且在构造函数里面还允许你放任意的 U1 和 U2, 不过 U1 和 U2 必须满足下面的赋值动作.
pair(const pair<U1, U2>& p):
first(p.first), second(p.second)
这个意思是把, 初值的头尾放进来当作我这个值本身的头尾. 所以初值是鲫鱼和麻雀, 放进来, 鲫鱼是一种鱼类所以这里可以转型, 同样的道理麻雀是一种鸟类.
你会在标准库很多地方看到这种构造函数, 是为了让构造函数更加有弹性一些.
如果我放的构造函数初值是鲸鱼和麻雀, 这里就会出错, 因为鲸鱼不属于鱼类. 这一块本身是正确的, 但是使用的时候失败了.
下面再举另外一个例子
我们前面已经说过 shared pointer, 说它是一种智能指针
在它的源代码里面, 构造函数, explicit
前面解释过
它这个用意是, shared pointer 本身允许你使用的时候给它指定任意的 type, 而构造函数说
如果我有一个聪明的指针指向鱼类, 现在我给它设初值, 我给设的指针指向鲫鱼
鲫鱼继承鱼类
template<typename _Tp>
class shared_ptr:public __shared_ptr<_Tp>
{
//...
template<typename _Tp1>
explicit shared_ptr(_Tp1* __p)
: __shared_ptr<_Tp>(__p) { }
//...
};
如果我 new 一个鲫鱼, 但是我的指针指向鱼类, 这是可以的, 这叫作 up-cast.
在面向对象的关系里, 由于父类往往是在上头, 子类在下头, 所以这个动作有点像指针要往上移动. 你有一个指针, 类型指向动物, 将来让它指向一头猪也是可以的, 因为猪也是动物.
所以下面一定是正确的.
Base1* ptr = new Derived1; // up-cast
shared_ptr<Base1>sptr(new Derived1); // 模拟 up-cast
现在我们说的是智能指针, 既然一般的指针可以这么做, 那么我智能指针也必须可以这么做. 所以下面模拟 up-cast 也是对的. 要这么做, 智能指针要写出 member template, 它的构造函数要写成上面那样.