首页 > 学院 > 开发设计 > 正文

C++箴言:用成员函数模板接受兼容类型

2019-11-17 05:20:45
字体:
来源:转载
供稿:网友

  smart pointers(智能指针)是行为很像指针但是增加了指针没有提供的功能的 objects。例如,《C++箴言:使用对象治理资源》阐述了标准 auto_ptr 和 tr1::shared_ptr 是怎样被应用于在恰当的时间自动删除的 heap-based resources(基于堆的资源)的。STL containers 内的 iterators(迭代器)几乎始终是 smart pointers(智能指针);你绝对不能指望用 "++" 将一个 built-in pointer(内建指针)从一个 linked list(线性链表)的一个节点移动到下一个,但是 list::iterators 可以做到。

  real pointers(真正的指针)做得很好的一件事是支持 implicit conversions(隐式转换)。derived class pointers(派生类指针)隐式转换到 base class pointers(基类指针),pointers to non-const objects(指向非常量对象的指针)转换到 pointers to const objects(指向常量对象的指针),等等。例如,考虑在一个 three-level hierarchy(三层继续体系)中能发生的一些转换:

class Top { ... };
class Middle: public Top { ... };
class Bottom: public Middle { ... };
Top *pt1 = new Middle; // convert Middle* => Top*
Top *pt2 = new Bottom; // convert Bottom* => Top*
const Top *pct2 = pt1; // convert Top* => const Top*
  在 user-defined smart pointer classes(用户定义智能指针类)中模拟这些转换是需要技巧的。我们要让下面的代码能够编译:

template<typename T>
class SmartPtr {
 public: // smart pointers are typically
  eXPlicit SmartPtr(T *realPtr); // initialized by built-in pointers
 ...
};

SmartPtr<Top> pt1 = // convert SmartPtr<Middle> =>
SmartPtr<Middle>(new Middle); // SmartPtr<Top>

SmartPtr<Top> pt2 = // convert SmartPtr<Bottom> =>
SmartPtr<Bottom>(new Bottom); // SmartPtr<Top>

SmartPtr<const Top> pct2 = pt1; // convert SmartPtr<Top> =>
// SmartPtr<const Top>
  在同一个 template(模板)的不同 instantiations(实例化)之间没有 inherent relationship(继续关系),所以编译器认为 SmartPtr<Middle> 和 SmartPtr<Top> 是完全不同的 classes,并不比(比方说)vector<float> 和 Widget 的关系更近。为了得到我们想要的在 SmartPtr classes 之间的转换,我们必须显式地为它们编程。

  在上面的 smart pointer(智能指针)的示例代码中,每一个语句创建一个新的 smart pointer object(智能指针对象),所以现在我们就集中于我们如何写 smart pointer constrUCtors(智能指针的构造函数),让它以我们想要的方式运转。一个要害的事实是我们无法写出我们需要的全部 constructors(构造函数)。在上面的 hierarchy(继续体系)中,我们能从一个 SmartPtr<Middle> 或一个 SmartPtr<Bottom> 构造出一个 SmartPtr<Top>,但是假如将来这个 hierarchy(继续体系)被扩充,SmartPtr<Top> objects 还必须能从其它 smart pointer types(智能指针类型)构造出来。例如,假如我们后来加入

class BelowBottom: public Bottom { ... };
  我们就需要支持从 SmartPtr<BelowBottom> objects 到 SmartPtr<Top> objects 的创建,而且我们当然不希望为了做到这一点而必须改变 SmartPtr template。

  大体上,我们需要的 constructors(构造函数)的数量是无限的。因为一个 template(模板)能被实例化而产生无数个函数,所以似乎我们不需要为 SmartPtr 提供一个 constructor function(构造函数函数),我们需要一个 constructor template(构造函数模板)。这样的 templates(模板)是 member function templates(成员函数模板)(经常被恰如其分地称为 member templates(成员模板))——生成一个 class 的 member functions(成员函数)的 templates(模板)的范例:

template<typename T>
class SmartPtr {
 public:
  template<typename U> // member template
  SmartPtr(const SmartPtr<U>& other); // for a "generalized
  ... // copy constructor"
};
  这就是说对于每一种类型 T 和每一种类型 U,都能从一个 SmartPtr<U> 创建出一个 SmartPtr<T>,因为 SmartPtr<T> 有一个取得一个 SmartPtr<U> 参数的 constructor(构造函数)。像这样的 constructor(构造函数)——从一个类型是同一个 template(模板)的不同实例化的 object 创建另一个 object 的 constructor(构造函数)(例如,从一个 SmartPtr<U> 创建一个 SmartPtr<T>)——有时被称为 generalized copy constructors(泛型化拷贝构造函数)。

  上面的 generalized copy constructor(泛型化拷贝构造函数)没有被声明为 explicit(显式)的。这是故意为之的。built-in pointer types(内建指针类型)之间的类型转换(例如,从派生类指针到基类指针)是隐式的和不需要 cast(强制转型)的,所以让 smart pointers(智能指针)模拟这一行为是合理的。在 templatized constructor(模板化构造函数)中省略 explicit 正好做到这一点。

  作为声明,SmartPtr 的 generalized copy constructor(泛型化拷贝构造函数)提供的东西比我们想要的还多。是的,我们需要能够从一个 SmartPtr<Bottom> 创建一个 SmartPtr<Top>,但是我们不需要能够从一个 SmartPtr<Top> 创建一个 SmartPtr<Bottom>,这就像颠倒 public inheritance(公有继续)的含义(参见《C++箴言:确保公开继续模拟“is-a”》)。我们也不需要能够从一个 SmartPtr<double> 创建一个 SmartPtr<int>,因为这和从 int* 到 double* 的 implicit conversion(隐式转换)是不相当的。我们必须设法过滤从这个 member template(成员模板)生成的 member functions(成员函数)的群体。

  假如 SmartPtr 跟随 auto_ptr 和 tr1::shared_ptr 的脚步,提供一个返回被这个 smart pointer(智能指针)持有的 built-in pointer(内建指针)的拷贝的 get member function(get 成员函数)(参见《C++箴言:在资源治理类中预备访问裸资源》),我们可以用 constructor template(构造函数模板)的实现将转换限定在我们想要的范围:

template<typename T>
class SmartPtr {
 public:
  template<typename U>
  SmartPtr(const SmartPtr<U>& other) // initialize this held ptr
  : heldPtr(other.get()) { ... } // with other's held ptr

  T* get() const { return heldPtr; }
  ...

 PRivate: // built-in pointer held
  T *heldPtr; // by the SmartPtr
};

  三层交换技术 交换机与路由器密码恢复 交换机的选购 路由器设置专题 路由故障处理手册 数字化校园网解决方案   我们通过 member initialization list(成员初始化列表),用 SmartPtr<U> 持有的类型为 U* 的指针初始化 SmartPtr<T> 的类型为 T* 的 data member(数据成员)。这只有在“存在一个从一个 U* 指针到一个 T* 指针的 implicit conversion(隐式转换)”的条件下才能编译,而这正是我们想要的。最终的效果就是 SmartPtr<T> 现在有一个 generalized copy constructor(泛型化拷贝构造函数),它只有在传入一个 compatible type(兼容类型)的参数时才能编译。

  member function templates(成员函数模板)的用途并不限于 constructors(构造函数)。它们的另一个常见的任务是用于支持 assignment(赋值)。例如,TR1 的 shared_ptr(再次参见《C++箴言:使用对象治理资源》)支持从所有兼容的 built-in pointers(内建指针),tr1::shared_ptrs,auto_ptrs 和 tr1::weak_ptrs构造,以及从除 tr1::weak_ptrs 以外所有这些赋值。这里是从 TR1 规范中摘录出来的一段关于 tr1::shared_ptr 的内容,包括它在声明 template parameters(模板参数)时使用 class 而不是 typename 的偏好。(就像《C++箴言:理解typename的两个含义》中阐述的,在这里的上下文环境中,它们的含义严格一致。)

template<class T> class shared_ptr {
public:
 template<class Y> // construct from
 explicit shared_ptr(Y * p); // any compatible
 template<class Y> // built-in pointer,
 shared_ptr(shared_ptr<Y> const& r); // shared_ptr,
 template<class Y> // weak_ptr, or
 explicit shared_ptr(weak_ptr<Y> const& r); // auto_ptr
 template<class Y>
 explicit shared_ptr(auto_ptr<Y>& r);
 template<class Y> // assign from
 shared_ptr& Operator=(shared_ptr<Y> const& r); // any compatible
 template<class Y> // shared_ptr or
 shared_ptr& operator=(auto_ptr<Y>& r); // auto_ptr
 ...
};
  除了 generalized copy constructor(泛型化拷贝构造函数),所有这些 constructors(构造函数)都是 explicit(显式)的。这就意味着从 shared_ptr 的一种类型到另一种的 implicit conversion(隐式转换)是被答应的,但是从一个 built-in pointer(内建指针)或其 smart pointer type(智能指针类型)的 implicit conversion(隐式转换)是不被许可的。(explicit conversion(显式转换)——例如,经由一个 cast(强制转型)——还是可以的。)同样引起注重的是 auto_ptrs 被传送给 tr1::shared_ptr 的 constructors(构造函数)和 assignment operators(赋值操作符)的方式没有被声明为 const,于此对照的是 tr1::shared_ptrs 和 tr1::weak_ptrs 的被传送的方式。这是 auto_ptrs 被复制时需要独一无二的被改变的事实的一个必然结果(参见《C++箴言:使用对象治理资源》)。

  member function templates(成员函数模板)是一个极好的东西,但是它们没有改变这个语言的基本规则。《C++箴言:了解C++偷偷加上和调用了什么》阐述的编译器可以产生的四个 member functions(成员函数)其中两个是 copy constructor(拷贝构造函数)和 copy assignment operator(拷贝赋值运算符)。tr1::shared_ptr 声明了一个 generalized copy constructor(泛型化拷贝构造函数),而且很明显,当类型 T 和 Y 相同时,generalized copy constructor(泛型化拷贝构造函数)就能被实例化而成为 "normal" copy constructor(“常规”拷贝构造函数)。那么,当一个 tr1::shared_ptr object 从另一个相同类型的 tr1::shared_ptr object 构造时,编译器是为 tr1::shared_ptr 生成一个 copy constructor(拷贝构造函数),还是实例化 generalized copy constructor template(泛型化拷贝构造函数模板)?

  就像我说过的,member templates(成员模板)不改变语言规则,而且规则规定假如一个 copy constructor(拷贝构造函数)是必需的而你没有声明,将为你自动生成一个。在一个 class 中声明一个 generalized copy constructor(泛型化拷贝构造函数)(一个 member template(成员模板))不会阻止编译器生成它们自己的 copy constructor(拷贝构造函数)(非模板的),所以假如你要全面支配 copy construction(拷贝构造),你必须既声明一个 generalized copy constructor(泛型化拷贝构造函数)又声明一个 "normal" copy constructor(“常规”拷贝构造函数)。这同样适用于 assignment(赋值)。这是从 tr1::shared_ptr 的定义中摘录的一段,可以作为例子:


template<class T> class shared_ptr {
 public:
  shared_ptr(shared_ptr const& r); // copy constructor

  template<class Y> // generalized
  shared_ptr(shared_ptr<Y> const& r); // copy constructor

  shared_ptr& operator=(shared_ptr const& r); // copy assignment
 
  template<class Y> // generalized
  shared_ptr& operator=(shared_ptr<Y> const& r); // copy assignment
  ...
};
  Things to Remember

  ·使用 member function templates(成员函数模板)生成接受所有兼容类型的函数。

  ·假如你为 generalized copy construction(泛型化拷贝构造)或 generalized assignment(泛型化赋值)声明了 member templates(成员模板),你依然需要声明 normal copy constructor(常规拷贝构造函数)和 copy assignment operator(拷贝赋值运算符)。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表