运用成员函数模板接受所有兼容类型——条款45

        所谓智能指针是“行为像指针”的对象,并提供指针没有的机能。例如条款13曾经提及std::auto_ptr和tr1::shared_ptr如何能够被用来在正确时机自动删除heap-based资源。STL容器的迭代器几乎总是智能指针;无疑地你不会奢望使用“++”将一个内置指针从linked list的某个节点移到另一个节点,但这在list::iterators身上办得到。

        真实指针做得很好的一件事是,支持隐式转换。Derived class指针可以隐式转换为base class指针,“指向non-const对象”的指针可以转换为“指向const对象” ......等等。下面是可能发生于三层继承体系的一些转换:

class Top { ... };
class Middle: public Top { ... };
class Bottom: public Middle { ... };
Top* pt1 = new Middle;                // 将Middle*转换为Top*
Top* pt2 = new Bottom;                // 将Bottom*转换为Top*
const Top* pct2 = pt1;                // 将Top*转换为const Top*

        但如果想在用户自定的智能指针中模拟上述转换,稍稍有点麻烦。我们希望以下代码通过编译:

template<typename T>
class SmartPtr {
    public:                                          // 智能指针通常以内置指针完成初始化
    explicit SmartPtr(T* realPtr);
    ...
};
SmartPtr<Top> pt1 = SmartPtr<Middle>(new Middle);   // 将SmartPtr<Middle>转SmartPtr<Top>
SmartPtr<Top> pt2 = SmartPtr<Bottom>(new Bottom);   // 将SmartPtr<Bottom>转SmartPtr<Top>
SmartPtr<const Top> pct2 = pt1;                     // 将SmartPtr<Top>转SmartPtr<const Top>

        但是,同一个template的不同具现体之间并不存在什么与生俱来的固有关系(译注:这里意指如果以带有base-derived关系的B,D两类型分别具现化某个template,产生出来的两个具现体并不带有base-derived关系),

        在上述智能指针实例中,每一个语句创建了一个新式智能指针对象,所以现在我们应该关注如何编写智能指针的构造函数,使其行为能够满足我们转型需要。一个很关键的观察结果是:我们永远无法写出我们需要的所有构造函数。在上述继承体系中,我们根据一个SmartPtr<Middle>或一个SmartPtr<Bottom>构造出一个SmartPtr<Top>,但如果这个继承体系未来有所扩充,SmartPtr<Top>对象又必须能够根据其他智能指针构造自己。假设日后添加了:

class BelowBottom: public Bottom { ... };

        我们因此必须令SmartPtr<BelowBottom>对象得以生成SmartPtr<Top>对象,但我们当然不希望一再修改SmartPtr template以满足此类需求。

        就原理而言,此例中我们需要的构造函数数量没有止尽,因为一个template可被无限量具现化,已致生成无限量函数。因此,似乎我们需要的不是为SmartPtr写一个构造函数,而是为它写一个构造模板。这样的模板是所谓member function templates,其作用是为class生成函数:

template<typename T>
class SmartPtr {
public:
    template<typename U>                  // member template,
    SmartPtr(const SmartPtr<U>& other);   // 未来生成copy构造函数
    ...
};

        以上代码的意思是,对任何类型T和任何类型U,这里可以根据SmartPtr<U>生成一个SmartPtr<T>——因为SmartPtr<T>有个构造函数接受一个SmartPtr<U>参数。这一类构造函数根据对象u创建对象t,而u和v的类型是同一个template的不同具现体,有时我们称之为泛化copy构造函数。

        上面的泛化copy构造函数并未被声明为explicit。那是蓄意的,因为原始指针类型之间的转换(例如从derived class指针转为base class指针)是隐式转换,无需明白写出转型动作(cast),所以让智能指针效仿这种行径也属合理。在模板化构造函数中略去explicit就是为了这个目的。

        完成声明之后,这个为SmartPtr而写的“泛化copy构造函数”提供的东西比我们需要的更多。是的,我们希望根据一个SmartPtr<Bottom>创建一个SmartPtr<Top>,却不希望根据一个SmartPtr<Top>创建一个SmartPtr<Bottom>,因为那对public继承而言(见条款32)是矛盾的。我们也不希望根据一个SmartPtr<double>创建一个SmartPtr<int>,因为现实中并没有“将int* 转换为double*”的对应隐式转换行为。是的,我们必须从某方面对这一member template所创建的成员函数群进行筛除。

        假设SmartPtr遵循auto_ptr和tr1::shared_ptr所提供的榜样,也提供一个get成员函数,返回智能指针对象(见条款15)所持有的那个原始指针的副本,那么我们可以在“构造模板”实现代码中约束转换行为,使它符合我们的期望:

template<typename T>
class SmartPtr {
public:
    template<typename U>
    SmartPtr(const SmartPtr<U>& other)  // 以other的heldPtr初始化this的heldPtr
    : heldPtr(other.get()) { ... }
    T* get() const { return heldPtr; }
    ...
private:
    T* heldPtr;      // 这个SmartPtr持有内置指针
};

        我使用成员初值列来初始化SmartPtr<T>之内类型为T*的成员变量,并以类型为U*的指针(由SmartPtr<U>持有)作为初值。这个行为只有当“存在某个隐式转换可将一个U*指针转为一个T*指针”时才能通过编译,而那正是我们想要的。最终效益时SmartPtr<T>现在有了一个泛化copy构造函数,这个构造函数只在其所获得的实参隶属适当(兼容)类型时才通过编译。

        member function templates的效用不限于构造函数,它们常扮演的另一个角色是支持赋值操作。例如TR1的shared_ptr(见条款13)支持所有“来自兼容之内置指针、tr1::shared_ptrs、auto_ptrs和tr1::weak_ptrs(见条款54)”的构造行为,以及所有来自上述各物(tr1::weak_ptrs除外)的赋值操作。下面是TR1规范中关于tr1::shared_ptr的一份摘录。

template<class T>
class shared_ptr {
public:
    template <class Y>                 
     explicit shared_ptr(Y* p);                        // 构造,来自任何兼容的内置指针
    template <class Y>
     shared_ptr(shared_ptr<Y> const& r);               // 或shared_ptr
    template <class Y>
     explicit shared_ptr(weak_ptr<Y> const& r);        // 或weak_ptr
    template <class Y>
     explicit shared_ptr(auto_ptr<Y>& r);              // 或auto_ptr
    template <class Y>                                 
     shared_ptr& operator=(shared_ptr<Y> const& r);    // 赋值,来自任何兼容的shared_ptr
    template <class Y>
     shared_ptr& operator=(auto_ptr<Y>& r);            // 或auto_ptr 
    ...
};

        上述所有构造函数都是explicit,唯有“泛化copy构造函数”除外。那意味从某个shared_ptr类型隐式转换至另一个shared_ptr类型是被允许的,但从某个内置指针或从其他智能指针类型进行隐式转换则不被认可(如果是显示转换和cast强制转型动作倒是可以)。另一个趣味点是传递给tr1::shared_ptr构造函数和assignment操作符的auto_ptrs并未被声明为const,与之形成对比的则是tr1::shared_ptrs和tr1::weak_ptrs都以const传递。这是因为条款13说过,当你复制一个auto_ptrs,它们其实被改动了。

        member templates并不改变语音规则,而语言规则说,如果程序需要一个copy构造函数,你却没有声明它,编译器会为你暗自生成一个。在class内声明泛化copy构造函数并不会阻止编译器生成它们自己的copy构造函数,所以如果你想要控制copy构造函数的方方面面,你必须同时声明泛化copy构造函数和“正常的”copy构造函数。相同规则也适用于赋值操作。下面是tr1::shared_ptr的一份定义摘要,例证上述所言:

template<class T>
class shared_ptr {
public:
    shared_ptr(shared_ptr const& r);    // copy构造函数

    template <class Y>
     shared_ptr(shared_ptr<Y> const& r);               // 泛化copy构造函数
                
     shared_ptr& operator=(shared_ptr<Y> const& r);    // copy assignment
    template <class Y>
     shared_ptr& operator=(shared_ptr<Y> const& );     // 泛化copy assignment
    ...
};

请记住

  • 请使用member function templates(成员函数模板)生成“可接受所有兼容类型”的函数。
  • 如果你声明member templates用于“泛化copy构造”或“泛化assignment操作”你还是需要声明正常的copy构造函数和copy assignment操作符。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值