资讯动态

Effective C++ 学习笔记 条款45 运用成员函数模板接受所有兼容类型

发布时间:2026/8/31 15:00:07 来源:尧图企业网站定制
智能指针是行为很像指针但提供了指针所不具备之功能的对象。例如条款13解释了标准库中的 auto_ptr 和 tr1::shared_ptr 如何能在适当的时机自动删除堆上分配的资源。STL 容器的迭代器几乎都是智能指针你当然不能指望通过“”将一个内置指针从链表的一个节点移到下一个节点但对于 list::iterator 来说这却是可行的。真实指针做得很好的事情之一是支持隐式转换。派生类指针隐式转换为基类指针指向非 const 对象的指针转换为指向 const 对象的指针等等。例如考虑在一个三层继承体系中可能发生的一些转换在用户自定义的智能指针类中模拟这样的转换是棘手的。我们需要让以下代码能够编译同一模板的不同实例化之间没有内在关系所以编译器把SmartPtrMiddle和SmartPtrTop视为完全不同的类它们之间的关联并不比vectorfloat和Widget更密切。为了获得我们想要的 SmartPtr 类之间的转换我们必须显式地对它们进行编程。在之前的智能指针示例代码中每条语句都创建了一个新的智能指针对象所以现在我们重点讨论如何编写行为符合我们预期的智能指针构造函数。一个关键的观察是我们无法写出所有我们需要的构造函数。在上面的继承体系中我们可以从SmartPtrMiddle或SmartPtrBottom构造SmartPtrTop但如果未来扩展了继承体系SmartPtrTop对象还必须能够从其他智能指针类型构造。例如如果我们后来添加了我们就需要支持从SmartPtrBelowBottom对象创建SmartPtrTop对象而我们当然不想为了这个而修改 SmartPtr 模板。从原则上讲我们需要的构造函数数量是无限的。既然模板可以被实例化以生成无限数量的函数看来我们需要的不是 SmartPtr 的一个构造函数而是一个构造函数模板。这类模板就是成员函数模板通常简称为成员模板的例子——即生成类的成员函数的模板这表示对于每一个类型 T 和每一个类型 U都可以从一个SmartPtrU创建一个SmartPtrT因为SmartPtrT有一个接受SmartPtrU参数的构造函数。像这样的构造函数——从一个类型为同一模板的不同实例化的对象创建另一个对象的构造函数例如从SmartPtrU创建SmartPtrT——有时被称为泛化拷贝构造函数generalized copy constructors。上面的泛化拷贝构造函数没有声明为 explicit。这是有意为之的。内置指针类型之间的类型转换例如从派生类指针到基类指针是隐式的不需要强制转型所以智能指针模拟这种行为是合理的。省略模板化构造函数上的 explicit 正好做到了这一点。按照上述声明SmartPtr 的泛化拷贝构造函数提供的功能超出了我们想要的。是的我们希望能够从SmartPtrBottom创建SmartPtrTop但我们不希望能够从SmartPtrTop创建SmartPtrBottom因为那违背了 public 继承的含义参见条款32。我们也不希望能够从SmartPtrdouble创建SmartPtrint因为不存在从 double* 到 int* 的相应隐式转换。在某种程度上我们必须对这个成员模板将要生成的成员函数进行筛选。假设 SmartPtr 仿照 auto_ptr 和 tr1::shared_ptr 的做法提供了一个 get 成员函数该函数返回智能指针对象所持有的内置指针的一份副本参见条款15那么我们可以利用构造函数模板的实现来将转换限制为我们想要的类型我们使用成员初始化列表来将SmartPtrT中类型为 T* 的数据成员用SmartPtrU所持有的类型为 U* 的指针来初始化。只有当存在从 U* 指针到 T* 指针的隐式转换时这段代码才能编译而这正是我们想要的。最终效果是SmartPtrT现在拥有一个泛化拷贝构造函数只有当传入一个兼容类型的参数时才会编译通过。成员函数模板的用途并不仅限于构造函数。另一个常见的角色是支持赋值操作。例如TR1 的 shared_ptr同样参见条款13支持从所有兼容的内置指针、tr1::shared_ptr、auto_ptr 和 tr1::weak_ptr参见条款54进行构造并且还支持从除 tr1::weak_ptr 以外的所有这些类型进行赋值。下面是 TR1 规范中关于 tr1::shared_ptr 的一段摘录其中还包括它在声明模板参数时偏好使用 class 而非 typename 的习惯如条款42所解释的在此上下文中它们含义完全相同这些构造函数除了泛化拷贝构造函数之外都被声明为 explicit。这意味着允许从一种类型的 shared_ptr 到另一种类型的 shared_ptr 的隐式转换但不允许从内置指针或其他智能指针类型的隐式转换显式转换——比如通过强制转型——是允许的。同样有趣的是传递给 tr1::shared_ptr 构造函数和赋值操作符的 auto_ptr 参数没有被声明为 const这与 tr1::shared_ptr 和 tr1::weak_ptr 的传递方式形成对比。这是因为 auto_ptr 在被拷贝时会被修改这一点上它是独一无二的参见条款13。成员函数模板是很棒的东西但它们并不改变语言的基本规则。条款5解释说编译器可能生成的四个成员函数中有两个是拷贝构造函数和拷贝赋值操作符。tr1::shared_ptr 声明了一个泛化拷贝构造函数而且很明显当类型 T 和 Y 相同时泛化拷贝构造函数可以被实例化以创建“常规”拷贝构造函数。那么当从一个 tr1::shared_ptr 对象构造另一个同类型的 tr1::shared_ptr 对象时编译器是生成一个拷贝构造函数还是实例化泛化拷贝构造函数模板呢如我所说成员模板并不改变语言的规则而规则规定如果需要拷贝构造函数而你没有声明一个编译器会自动为你生成一个。在类中声明一个泛化拷贝构造函数一个成员模板并不能阻止编译器生成它们自己的拷贝构造函数一个非模板。因此如果你想控制拷贝构造的所有方面就必须同时声明泛化拷贝构造函数和“常规”拷贝构造函数。赋值操作也是如此。下面是一段摘自 tr1::shared_ptr 定义的示例说明了这一点切记1.使用成员函数模板来生成接受所有兼容类型的函数。2.如果你声明了用于泛化拷贝构造或泛化赋值的成员模板你仍然需要同时声明常规的拷贝构造函数和拷贝赋值操作符。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价