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

发布时间:2024年01月19日

1.前言

所谓智能指针(smart pointers)是”行为像指针“的对象,并提供指针没有的机能,比如条款13提到的std::auto_ptr和trl::shared_ptr如何被用来在正确时机自动删除heap-based资源。STL容器的迭代器几乎总是智能指针。

真实指针做的很好的一个地方就是支持隐式转换(implicit conversions)。Derived class指针可以隐式转换为base class指针,“指向non-const对象”的指针可以转换为“指向const对象”,下面是可能发生于三层继承体系的一些转换

class Top{....};
class Middle:public Middle {....};
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的不同具现体(instantiations)之间并不存在什么固有关系(假设两个以带有base-derived关系的B,D两类型分别具现化某个template,产生出来的两个具现体并不带有base-derived关系),所以编译器SmartPtr<Middle>和SmartPtr<Top>为完全不同的classes。所以为了将我们希望获得的SmartPtr classes之间的转换能力,必须把它们明确地写出来:

2.场景1:Templates和泛型编程(Generic Programming)

在上述智能指针实例中,每一个语句创建了一个新式智能指针对象,所以现在我们需要关注如何编写智能指针地构造函数,使其行为能够满足我们地转型需要。一个很重要地观察结果是:我们永远无法写出我们需要地所有构造函数。

在上述继承体系中,我们根据一个SmartPtr<Middle>或一个SmartPtr<Bottom>构造出一个SmartPtr<Top>,但如果这个继承体系未来有所扩充,SmartPtr<Top>对象必须能够根据其它智能指针构造自己:

class BelowBottom:public Bottom {....};

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

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

template<typename T>
class SmartPtr{

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

};

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

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

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

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

template<typename T>
class SmartPtr{

    public:
        template<typename U>

        SmartPtr(const SmartPtr<U>& other):heldPtr(const SmartPtr<U> other) //以other的                
                                                           //heldPtr初始化this的heldPtr
        {
           .... 
        }

        T* get() const
        {
            return heldPtr;    //这个SmartPtr持有的内置指针
        }   
        ...
     private:
        T* Heldptr;
}

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

member function templates的效用不限于构造函数,它们常扮演的另一个角色是支持赋值操作。例如TR1的share_ptr支持所有“来自兼容之内置指针,trl::shared_potrs,auto_ptrs和trl::weak_ptrs”的构造行为,以及所有来自上述各物的赋值操作。下面是TR1规范中关于tr1::shared_ptr的一份摘录,其中强烈倾向声明template参数时采用关键字class而不采用typename。

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或        
                                                      //auto_ptr
        template<class Y>
        shared_ptr& operator=(auto_ptr<Y>& r);
        ....

};

上述所有构造函数都是explicit,唯有“泛化copy构造函数”除外。那以外着从某个shared_ptr类型隐式转换到另一个shared_ptr类型是被允许的,但从某个内置指针或从其它智能指针类型进行隐式转换则不被认可(如果是显式转换如cast强制转型动作到是可以)。另一个趣味点是传递给trl::shared_ptr构造函数和assignment操作符的auto_ptr并未声明为const,与之形成鲜明对比的则是tr1::shared_ptr和tr1::weak_ptrs都以const传递。

3.总结

(1)请使用member function templates生成“可接受所有兼容类型”的函数

(2)如果你声明member templates用于“泛化构造”或“泛化assignment操作”,还是需要声明正常的copy构造函数和copy assignment操作符。

文章来源:https://blog.csdn.net/T15779651370/article/details/135694762
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。