读书笔记 effective c++ Item 45 使用成员函数模板来接受“所有兼容类型”
智能指针的行为像是指针,但是没有提供加的功能。例如,Item 13中解释了如何使用标准auto_ptr和tr1::shared_ptr指针在正确的时间自动删除堆上的资源。STL容器中的迭代器基本上都是智能指针:当然,你不能通过使用“++”来将链表中的指向一个节点的内建指针移到下一个节点上去,但是list::iterator可以这么做。
1. 问题分析——如何实现智能指针的隐式转换
真正的指针能够做好的一件事情是支持隐式转换。派生类指针可以隐式转换为基类指针,指向非const的指针可以隐式转换成为指向const对象的指针,等等。例如,考虑可以在一个三层继承体系中发生的转换:
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*
在用户自定义的智能指针中模仿这种转换是很微妙的。我们想让下面的代码通过编译:
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>
同一个模板的不同实例之间没有固有的关系,所以编译器将SmartPtr<Middle>和SmartPtr<Top>视为完全不同的类,它们之间的关系不比vector<float>和Widget来的近。为了实现SmartPtr类之间的转换,我们必须显示的实现。
在上面的智能指针示例代码中,每个语句都创建了一个新的智能指针对象,所以现在我们把焦点放在如何实现出一个行为表现如我们所愿的智能指针构造函数。关键的一点是没有办法实现我们需要的所有构造函数。在上面的继承体系中,我们可以用一个SmartPtr<Middle>或一个SmartPtr<Bottom>来构造一个SmartPtr<Top>,但是如果这个继承体系在未来扩展了,SmartPtr<Top>对象必须能够从其他智能指针类型中构造出来。例如,如果我们增加了下面的类:
class BelowBottom: public Bottom { ... };
我们将会需要支持用SmartPtr<BelowBottom>对象来创建SmartPtr<Top>对象,我们当然不想通过修改SmartPtr模板来实现它。
2. 使用成员函数模板——泛化拷贝构造函数进行隐式转换
从原则上来说,我们所需要的构造函数的数量是没有限制的。既然模板可以被实例化成为没有限制数量的函数,因此看上去我们不需要一个SmartPtr的构造函数,我们需要的是一个构造函数模板。这样的模板是成员函数模板(member function templates) (也被叫做member 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<T>能够用SmartPtr<U>创造出来,因为SmartPtr<T>有一个以SmartPtr<U>作为参数的构造函数 。像这样的构造函数——用一个对象来创建另外一个对象,两个对象来自于相同的模板但是它们为不同类型(例如,用SmartPtr<U>来创建SmartPtr<T>),它通常被叫做泛化拷贝构造函数(generalized copy constructors)。
2.1 隐式转换不需要explicit
上面的泛化拷贝构造函数并没有被声明为explicit。这是经过仔细考虑的。内建指针类型之间的类型转换(例如从派生类转换到基类指针)是隐式的,并且不需要cast,因此智能指针模仿这种行为就是合理的。在模板化的构造函数上省略explicit正好做到了这一点。
2.2 将不符合要求的模板实例化函数剔除掉
为SmartPtr实现的泛化拷贝构造函数比我们想要的提供了更多的东西。我们想要用SmartPtr<Bottom>创建SmartPtr<Top>,但是我们不想用SmartPtr<Top>创建SmartPtr<Bottom>,因为这违背了public继承的含义(Item 32)。我们同样不想用SmartPtr<double>创建SmartPtr<int>,因为没有从double*到int*之间的隐式转换。因此,我们必须将成员模板生成的这种成员函数集合剔除掉。
假设SmartPtr遵循auto_ptr和tr1::shared_ptr的设计,也提供一个get成员函数来返回智能指针对象所包含的内建类型指针的一份拷贝(Item 15),我们可以使用构造函数模板的实现来对一些转换进行限制:
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
}
我们在成员初始化列表中用SmartPtr<U>中包含的类型为U*的指针来初始化SmartPtr<T>中的类型为T*的数据成员。这只有在能够从U*指针到T*指针进行隐式转换的情况下才能通过编译,这也正是我们所需要的。实际结果是现在SmartPtr<T>有了一个泛化拷贝构造函数,只有传递的参数为兼容类型时才能够通过编译。
3. 成员函数模板对赋值的支持
成员函数模板的使用不仅仅限定在构造函数上。它们的另外一个普通的角色是对赋值的支持。例如,tr1的shared_ptr(Item 13)支持用所有兼容的内建指针来对其进行构造,可以用tr1::shared_ptr,auto_ptr和tr1::weak_ptr(Item 54)来进行构造,对赋值也同样使用,但是tr1::weak_ptr例外。下面是从tr1的说明中摘录下来的tr1::shared_ptr的实现,可以看到在声明模板参数的时候它倾向于使用class而不是typename。(Item 42中描述的,在这个上下文中它们的意义相同。)
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
...
};
所有的这些构造函数都是explicit的,除了泛化拷贝构造函数。这就意味着从shared_ptr的一种类型隐式转换到shared_ptr的另一种类型是允许的,但是内建类型指针和其他的智能指针类型到shared_ptr的隐式转换是禁止的。(显示的转换是可以的(例如通过使用cast))。同样有趣的是传递给tr1::shared_ptr构造函数和赋值运算符的auto_ptr没有被声明为const,但是tr1::shared_ptr和tr1::weak_ptr的传递却声明为const了。这是因为auto_ptr被拷贝的时候已经被修改了(Item 13)。
4. 成员函数模板会生成默认拷贝构造函数
成员函数模板是美好的东西,但是它们没有修改语言的基本规则。Item 5解释了编译器会自动生成的4个成员函数中的两个函数为拷贝构造函数和拷贝赋值运算符。Tr1::shared_ptr声明了一个泛化拷贝构造函数,很清楚的是如果类型T和类型Y是相同的,泛化拷贝构造函数就会被实例化成一个“普通”的拷贝构造函数。那么编译器会为tr1::shared_ptr生成一个拷贝构造函数么?或者说用相同类型的tr1::shared_ptr构造另外一个tr1::shared_ptr的时候,编译器会实例化泛化拷贝构造函数么?
正如我所说的,成员模板没有修改语言的规则。“如果你需要一个拷贝构造函数而你没有自己声明,编译器会为你生成一个”这条规则也是其中之一。在一个类中声明一个泛化拷贝构造函数(一个member template)不会阻止编译器生成它们自己的拷贝构造函数(non-template),所以如果你想控制拷贝构造函数的所有方面,你必须同时声明一个泛化拷贝构造函数和“普通的”构造函数。对于赋值同样适用。下面是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
...
};
5. 总结
- 使用成员函数模板来生成接受所有兼容类型的函数。
- 如果你为泛化拷贝构造函数和泛化赋值运算符声明成员模板,你同样需要声明普通的拷贝构造函数和拷贝赋值运算符。
读书笔记 effective c++ Item 45 使用成员函数模板来接受“所有兼容类型”的更多相关文章
- 读书笔记 effective c++ Item 35 考虑虚函数的替代者
1. 突破思维——不要将思维限定在面向对象方法上 你正在制作一个视频游戏,你正在为游戏中的人物设计一个类继承体系.你的游戏处在农耕时代,人类很容易受伤或者说健康度降低.因此你决定为其提供一个成员函数, ...
- 读书笔记 effective c++ Item 43 了解如何访问模板化基类中的名字
1. 问题的引入——派生类不会发现模板基类中的名字 假设我们需要写一个应用,使用它可以为不同的公司发送消息.消息可以以加密或者明文(未加密)的方式被发送.如果在编译阶段我们有足够的信息来确定哪个信息会 ...
- 读书笔记 effective c++ Item 22 将数据成员声明成private
我们首先看一下为什么数据成员不应该是public的,然后我们将会看到应用在public数据成员上的论证同样适用于protected成员.最后够得出结论:数据成员应该是private的. 1. 为什么数 ...
- 读书笔记 effective c++ Item 23 宁可使用非成员非友元函数函数也不使用成员函数
1. 非成员非友元好还是成员函数好? 想象一个表示web浏览器的类.这样一个类提供了清除下载缓存,清除URL访问历史,从系统中移除所有cookies等接口: class WebBrowser { pu ...
- 读书笔记 effective c++ Item 14 对资源管理类的拷贝行为要谨慎
1. 自己实现一个资源管理类 Item 13中介绍了 “资源获取之时也是初始化之时(RAII)”的概念,这个概念被当作资源管理类的“脊柱“,也描述了auto_ptr和tr1::shared_ptr是如 ...
- 读书笔记 effective c++ Item 54 让你自己熟悉包括TR1在内的标准库
1. C++0x的历史渊源 C++标准——也就是定义语言的文档和程序库——在1998被批准.在2003年,一个小的“修复bug”版本被发布.然而标准委员会仍然在继续他们的工作,一个“2.0版本”的C+ ...
- 读书笔记 effective c++ Item 12 拷贝对象的所有部分
1.默认构造函数介绍 在设计良好的面向对象系统中,会将对象的内部进行封装,只有两个函数可以拷贝对象:这两个函数分别叫做拷贝构造函数和拷贝赋值运算符.我们把这两个函数统一叫做拷贝函数.从Item5中,我 ...
- 读书笔记 effective c++ Item 15 在资源管理类中提供对原生(raw)资源的访问
1.为什么需要访问资源管理类中的原生资源 资源管理类是很奇妙的.它们是防止资源泄漏的堡垒,没有资源泄漏发生是设计良好的系统的一个基本特征.在一个完美的世界中,你需要依赖这样的类来同资源进行交互,绝不 ...
- 读书笔记 effective c++ Item 18 使接口容易被正确使用,不容易被误用
1. 什么样的接口才是好的接口 C++中充斥着接口:函数接口,类接口,模板接口.每个接口都是客户同你的代码进行交互的一种方法.假设你正在面对的是一些“讲道理”的人员,这些客户尝试把工作做好,他们希望能 ...
随机推荐
- 【转】Nutch的Hadoop方式爬取效率优化
原文地址:http://my.oschina.net/junfrank/blog/290404
- 每天一个linux命令(49)--diff命令
diff 命令是 Linux 上非常重要的工具,用于比较文件的内容,特别是比较两个版本不同的文件以找到改动的地方.diff 在命令行中打印每一个行的改动.最新版本的diff还支持二进制文件,diff ...
- lxd-启动篇分析
lxd是什么:lxd是基于lxc构筑的容器管理进程,提供镜像,网络,存储,以及容器的能力,对外暴漏restfull API.其与docker的区别是docker更切近与app container,以应 ...
- maven构建maven-project和maven-module
在Eclipse中创建Maven多模块工程的例子 更多0 如果,你需要创建多个项目,项目之间即独立又有关系,那么创建一个Maven多模块项目是个非常好的选择,也非常cool!怎么在Eclipse里 ...
- Jmeter+Badboy实战经验二(使用jmeter)
1. 新建线程组: TestPlan--添加--Threads(Users)--线程组
- Java面试12|Linux及Shell脚本
1. 随便写一个awk的命令.用awk统计文本行数 (1)最近登录的5个帐号 last -n 5 | awk -F ':'(指定域分割符号) '{print $1}' 读入有'\n'换行符分割的一条记 ...
- 1088: [SCOI2005]扫雷Mine
1088: [SCOI2005]扫雷Mine Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 1635 Solved: 979[Submit][Sta ...
- SEO-站内优化规范
类别 要求 实际工作要求 程 序 设 计 1.DIV+CSS布局 2.站内导航连接性良好 面包屑导航,翻页方式使用样式二,文章和产品上一页和下一页 3.图片的ALT属性 在编程时注意写 4.超级链接的 ...
- Effective Modern C++ Item 37:确保std::thread在销毁时是unjoinable的
下面这段代码,如果调用func,按照C++的标准,程序会被终止(std::terminate) void func() { std::thread t([] { std::chrono::micros ...
- 分享自己使用CSS的public
body,ol,ul,h1,h2,h3,h4,h5,h6,p,th,td,dl,dd,form,fieldset,legend,input,textarea,select,td,figure{marg ...