C++之运用成员模板接受所有兼容类型(45)---《Effective C++》

来源:互联网 发布:太阳黑子数据下载 编辑:程序博客网 时间:2024/05/22 17:06

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

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

class Top{...};class Middle:public Top{...};class Bottom:public Middle{...};Top* ptr1=new Middle;Top* ptr2=new Bottom;const Top* ptr2=ptr1;

但如果想在用户自定义的指针中模拟上述转换,稍微有些麻烦,参看如下代码:

template <typename T>class SmartPtr{public:    explicit SmartPtr(T* realPtr);    ...};SmartPtr<Top> ptr1=SmartPtr<Middle>(new Middle>;SmartPtr<Top>ptr2=SmartPtr<Bottom>(new Bottom);SmartPtr<const Top> ptr2=ptr1;

注意,这里如果以带有base-derived关系的B、D两种类型分别具现化某个template,产生出来的两个局纤体并不带有base-derived关系,所以编译器视SmartPtr<<>Middle>和Smart<<>Top>为完全不同的两个classes,它们之间,关系并不比vector和Widget更加密切,因此,为了获得我们希望获得的SmartPtr classes之间的转换能力,我们必须将他们明确的编写出来。

Template和泛型编程(Generic Programming)

在上述的继承体系中,我们根据一个SmartPtr<<>Middle>或者一个SmartPtr<<>Bottom>构造出一个SmartPrt<<>Top>,但如果这个继承体系未来有所扩充,SmartPtr<<>Top>对象又必须能够更具其他智能指针构造自己,假设未来添加了:

class BelowBottom:public Bottom{...};

我们必须令Smart<<>BelowBottom>对象得以生成Smart<<>Top>对象,但我们当然不愿意一再修改SmartPtr template以满足此类需求。
因为template可以被无限量具现化,以致生成无限量函数,因此,似乎我们需要的不是为SmartPtr写一个构造函数,而是为它写一个构造模板,这样的模板称为:member function templates(简称为member templates),起作用是为class生成函数:

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

以上代码的意思是,对任何类型T和任何类型U,这里可以根据SmartPtr<<>U>生成一个SmartPtr<<>T>,因为SmartPtr<<>T>有个构造函数接受一个SmartPtr<<>U>创建一个SmartPtr<<>T>,而u和v的类型是同一个template的不同具现化,有时我们称为泛化copy构造函数(generalized copy constructor function),可以观察到上面的copy构造函数并未被声明为explicit,那是蓄意的,因为原始指针类型之间的转换是隐式转换,无需明白写出转型动作,所以让智能指针仿效这种行为也属合理,在模板化构造函数中略去explicit就是为了这个目的。

面临问题:

完成了该函数声明之后,这个为SmartPtr而写的“泛化copy构造函数”提供的东西比我们需要的更多,我们希望将子类指针转换为基类指针,如SmartPtr<<>Bottom>转化为SmartPtr<<>Top>,却不希望根据一个SmartPtr<<>Top>转化为SmartPtr<<>Bottom>,因为这与继承体系相违背,因此,我们必须在某方面对“member template”所创建的成员函数群进行挑选和筛除,从而选出我们所需要的函数。

解决办法:

template <typename T>class SmartPtr{public:    template <typename U>    SmartPtr(const SmartPtr<U>& other):heldPtr(ohter.get()){        ...    }    T* get() const{        return heldPtr;    }    ...private:    T* heldPtr;};

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

member function templates

member function templates(成员函数模板)的效用不限于构造函数,它们常常扮演的另一个角色是支持赋值操作,例如TRl的shared_ptr支持所有“来自兼容之内置指针、trl::shared_ptr、auto_ptr和trl::weak_ptr除外”的赋值操作,下面是TRl规范中的一些代码,参照一下:

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);    template <class Y>    explicit shared_ptr(weak_ptr<Y> const& r);    template <class Y>    explicit shared_ptr(auto_ptr<Y>& r);    template <class Y>    shared_ptr& operator=(shared_ptr<Y> const& r);    template <class Y>    shared_ptr& operator=(auto_ptr<Y>& r);    ...};

上述所有的构造函数都是explicit,唯有“泛化copy构造函数”除外,那意味这从某个shared_ptr类型隐式转换至另一个shared_ptr类型隐式转换至另一个shared_ptr类型是被允许的,但从某个内置指针或从其他只能指针类型进行隐式类型转换则不被认可(如果是显示转换如cast强制类型转换动作倒是可以),另一个比较奇怪的点是传递给trl::shared_ptr的构造函数和assignment操作符的auto_ptr和assignment操作符的auto_ptr并没有被声明为const,阈值形成对比的则是trl::shared_ptr和trl::weak_ptr都以const传递,这是因为前面讲过,当你复制一个auto_ptr时候,它们其实已经被改动过了。

member function templates是个奇妙的东西,但它们并不改变语言规则。编译器可能为我们生成四个成员函数,其中两个分别是copy construtctor function和copy assignment操作符,那么现在,trl::shared_ptr声明了一个泛化copy 构造函数,而显然一旦类型T和Y相同,泛化copy构造函数会被具现化为“正常的”copy构造函数。那么究竟编译器是否会为我们生成一个copy 构造函数呢?答案是yes,在class内声明繁华copy 构造函数(member template)并不会组织编译器生成自己的copy构造函数(non-template),所以如果你想要控制copy构造的方方面面,你必须同时声明泛化copy构造函数和“正常的”copy构造函数,相同规则也适用于赋值操作,可以参看如下代码:

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 const& r);//copy assignment操作符    template <class Y>    shared_ptr& operator=(shared_ptr<Y>const& r);//泛化copy assignment操作符    ...};

总结:
1)请使用member function templates(成员函数模板)生成“可接受所有兼容类型”的函数;
2)如果你声明member tempaltes用于“泛化copy构造”或者“泛化assignment操作符”,你还是需要声明正常的copy构造函数和copy assignment操作符。

阅读全文
0 0
原创粉丝点击