大家好,我是小贺。
点赞再看,养成习惯
文章每周持续更新,可以微信搜索「herongwei」第一时间阅读和催更,本文 GitHub : https://github.com/rongweihe/MoreThanCPlusPlus 已经收录,有一线大厂面试点思维导图,也整理了很多我的文档,欢迎 star 和完善。一起加油,变得更好!
前言上一篇,我们剖析了 STL 空间配置器,这一篇文章,我们来学习下 STL 迭代器以及背后的 traits 编程技法。
在 STL 编程中,容器和算法是独立设计的,容器里面存的是数据,而算法则是提供了对数据的操作,在算法操作数据的过程中,要用到迭代器,迭代器可以看做是容器和算法中间的桥梁。
1、迭代器设计模式为何说迭代器的时候,还谈到了设计模式?这个迭代器和设计模式又有什么关系呢?
其实,在《设计模式:可复用面向对象软件的基础》(GOF)这本经典书中,谈到了 23 种设计模式,其中就有 iterator 迭代模式,且篇幅颇大。
碰巧,笔者在研究 STL 源码的时候,同样的发现有 iterator 迭代器,而且还占据了一章的篇幅。
在设计模式中,关于 iterator 的描述如下:一种能够顺序访问容器中每个元素的方法,使用该方法不能暴露容器内部的表达方式。而类型萃取技术就是为了要解决和 iterator 有关的问题的。
有了上面这个基础,我们就知道了迭代器本身也是一种设计模式,其设计思想值得我们仔细体会。
那么 C++ STL 实现 iterator 和 GOF 介绍的迭代器实现方法什么区别呢? 那首先我们需要了解 C++ 中的两个编程范式的概念,OOP(面向对象编程)和 GP(泛型编程)。
在 C++ 语言里面,我们可用以下方式来简单区分一下 OOP 和 GP :
OOP:将 methods 和 datas 关联到一起 (通俗点就是方法和成员变量放到一个类中实现),通过继承的方式,利用虚函数表(virtual)来实现运行时类型的判定,也叫"动态多态",由于运行过程中需根据类型去检索虚函数表,因此效率相对较低。
GP:泛型编程,也被称为"静态多态",多种数据类型在同一种算法或者结构上皆可操作,其效率与针对某特定数据类型而设计的算法或者结构相同, 具体数据类型在编译期确定,编译器承担更多,代码执行效率高。在 STL 中利用 GP 将 methods 和 datas 实现了分而治之。
而 C++ STL 库的整个实现采用的就是 GP(Generic Programming),而不是 OOP(Object Oriented Programming)。而 GOF 设计模式采用的就是继承关系实现的,因此,相对来讲,C++ STL 的实现效率会相对较高,而且也更有利于维护。
在 STL 编程结构里面,迭代器其实也是一种模板 class ,迭代器在 STL 中得到了广泛的应用,通过迭代器,容器和算法可以有机的绑定在一起,只要对算法给予不同的迭代器,比如 vector::iterator、list::iterator,std::find() 就能对不同的容器进行查找,而无需针对某个容器来设计多个版本。
这样看来,迭代器似乎依附在容器之下,那么,有没有独立而适用于所有容器的泛化的迭代器呢?这个问题先留着,在后面我们会看到,在 STL 编程结构里面,它是如何把迭代器运用的炉火纯青。
2、智能指针STL 是泛型编程思想的产物,是以泛型编程为指导而产生的。具体来说,STL 中的迭代器将范型算法 (find, count, find_if) 等应用于某个容器中,给算法提供一个访问容器元素的工具,iterator 就扮演着这个重要的角色。
稍微看过 STL 迭代器源码的,就明白迭代器其实也是一种智能指针,因此,它也就拥有了一般指针的所有特点—— 能够对其进行 * 和 -> 操作。
template<typename T> class ListIterator {//mylist迭代器 public: ListIterator(T *p = 0) : m_ptr(p){} //构造函数 T& operator*() const { return *m_ptr;} //取值,即dereference T* operator->() const { return m_ptr;} //成员访问,即member access //... };但是在遍历容器的时候,不可避免的要对遍历的容器内部有所了解,所以,干脆把迭代器的开发工作交给容器的设计者,如此以来,所有实现细节反而得以封装起来不被使用者看到,这也正是为什么每一种 STL 容器都提供有专属迭代器的缘故。
比如笔者自己实现的 list 迭代器在这里使用的好处主要有:
(1) 不用担心内存泄漏(类似智能指针,析构函数释放内存);