Fork me on GitHub

如何自己写一个STL(上)

提前说明:整个C++中,其实并没有STL的概念,这个概念纯属是来自某本书的影响,而我在这也先限定一下:这里所讨论的”STL”主要指的是标准库中的迭代器库、容器库以及算法库的内容。

昨天,我的一个小项目 MyTinySTL 发布了 v2.0.0 版本,宣布这个项目告一段落。这个项目从去年暑假开始动工,至今已经过了一年多了,期间完成过多次重构,这一次算是第三次重构,进行了非常多的修改。可以看到,提交数已经超过 1000,当然我没有特地凑这个数字,而是我最后一次 merge 之后,刚好就是一千了!而代码量,现存下来的不是很多,2W 余行,但是期间 debug 过的代码量,远远超过这个数字。所以我也不会去求 star,但是能给就最好了!(〃’▽’〃)

bd9ba95504a190fds.jpg

af4dd627a3514c3c.jpg

其实这期间,有人向我反馈过,我有没有一些笔记什么的。然而一直没有什么时间做。我很高兴能够有人看一眼我的拙作(其实我收到的反馈也只有两个人),但更重要的是我自己的收获。所以现在,我基本也可以弃掉这个坑,去学习其它的东西了,就来说一些自己实现一个 tinystl 的感想吧!

为什么要自己实现一个 STL

这个问题在不同时候、不同的人身上,会有不同的意义。对于我个人而言,当初我的想法是什么呢?其实我很早之前就已经接触过和自学过 C++ 了,但那毕竟是太小的时候,以至于很多东西都不懂,所以也可以说根本不会。但也不是完全没有帮助,对我的帮助就是没有 “如何入门C++” 这一说。因为基本的语法之类的我也知道,只是不明白为什么这样写。所以等我到了大学,我只花了两天时间把几年前的一本 C++ 入门书翻一翻,就大概复习了一遍 C++ 的基本语法了。当然,书是 09 年买的,所以那个时候还没有 C++11,我也不会模板。所以自然跟很多初学者会产生同样的疑问:C++ 能做什么?

然后大一的时候,练习过一些竞赛题,用 C++ 写。那时候就只知道,C++ 有个东西,叫 STL。#include <algorithm> 后你可以直接用 sort,不用自己写;#include <vector> 后你可以用 vector,还不需要管它的长度;#include <map> 之后可以用 map,相当于自己用两个 raw array 来模拟……但是,为什么就可以用呢?为什么要写成 vector<int> 呢?这个<int>有什么用呢?……那时候我还真不懂,连模板都没听过。

后来,我希望找一些简单的练手项目,来练习一下 C++。但是好多人提的练手项目,我都看不懂,不知道他们在说什么,不知道初学者会不会也有这样的感受。然后我就看到有一个人说:实现一个 tinystl 。哎,这个我好像知道,然后看了看别人附上的链接,里面有 Vector,有 List,emmmmmm…我当时对 STL 的理解就是,把数据结构封装成库了嘛,就想着数据结构还是能看懂的,而且也希望了解一下 C++ 里面那些玩意儿是怎么实现的,立马就决定了 —— 就写一个 tinystl 了!

准备知识

决定好了,怎么开始呢?我看到很多人都提到一本书 —— 《STL源码剖析》,于是我自然也去买了这本书来看。当然现在让我推荐,我对这本书持保留态度。确实这本书很老,而且用的是 SGI 的实现。但是这本书是让我认识到了什么是 STL,STL 的构成等等等等。但是对于现在入门 C++ 都有什么 Primer 什么 Plus 什么 5 的,那么好的资源,应该这些东西都懂,根本不需要再看那本书了。书中提到,还需要一些模板的基础知识,所以我就在网上学习了一下 C++ 模板的基础知识,也是那种很零碎的一篇一篇博客,但知识够用了,因为 STL 中用到的技巧并不复杂。当然现在让我推荐 C++ 模板的教程的话,我自然会安利一波空大(空明流转)的这个 CppTemplateTutorial,还有我的 PR 呢,只不过空大太忙了没时间看。然后有了基础了解之后,就差不多可以动手了。当然这一篇文章我不会开始讲如何去写,我想先说说我个人的感受。

实现过程

说实话,tinystl 的规模可以说是要大就大,要小就小。小的可以很简单,大的可以很复杂。刚开始我也没想过有多少,也没想过要写得完善,没想到最后却跟这辣鸡标准库越来越接近…比如接口,会修改得跟标准一致,以及异常安全保证,也会尽量遵守标准。当然这也不全是坏事,因为你去实现一遍,你就能感受到很多设计极其不合理的地方…这样你再喷的时候才能有理有据
当然,实现过程免不了 “借鉴” 或者 “抄袭”,毕竟很多东西让你从 0 到 1,是很难做到的。但是你有了这个从 0 到 1 的基础之后,再往后 从 1 到 2 甚至到 n (n > 2),都是容易得多的。比如你多写几个 algorithm 里的函数,你就会发现套路其实都差不多的,大部分函数你都可以独自写出。只有少部分需要用到智商。容器的话,数据结构间的差异还是蛮大的(不要扯什么 set/map,我说的不是这个意思),所以具体实现有许多不同。但整体框架都保持一致,都是 constructor, destructor, operator=,迭代器相关、容量相关、插入删除相关,有些可能还有容器特有的操作等等。
所以不要担心它多,担心它难,只要开始去做,把开始的工作做好,剩下来的就是不断的糊各种各样的东西而已。当然,非常重要的一点,一定要在做之前对各种数据结构的原理了然于心。

有没有用

也会有人说,现在学 C++ 的造 STL 泛滥,各种 github 上抄来抄去,有什么用?当然啦,有没有用,这是很难讲的。你觉得有,可能就有;你觉得没有,也未必没有。也要考虑你出于什么目的,比如你是一个可以独自用 C++ 造出一个什么高性能高精度运算库,什么高性能 DB,什么游戏引擎的,对什么 template,什么 tag dispatch,什么 SFINAE 等技法早就烂熟于心的,你说造一个 tinystl 干嘛?别人根本就不屑用标准库的了,都有自己的一套。而如果说你是想提升 C++ 技能,学习 C++ 技巧,造一个 tinystl 其实并没有用到太多的技巧,讲真,大部分时间都是去堆砌、堆砌、堆砌,然后找 bug、找 bug、找 bug。
那么你说,造它有什么用呢?我个人觉得,学习不能抱着太强的目的,因为学习的过程本身就是未知的、曲折的、探索的,其中很有可能伴着失败,你要是抱着很强目的心去学习,学不到的,还有可能会走火入魔(雾)。
而于我而言,我完成这个,对我的帮助还蛮大。但主要不是在 STL 这一块。因为我在写这个之前,很少用多个头文件,源文件这样的,都是一个文件全搞定。所以这可以说是第一个“工程”类的项目了吧?所以我学到了很多代码结构,工程方面的东西。包括但不限于头文件 include 保护、代码规范等。说真的在代码规范这方面我为此看了非常多,包括但不限于 Google C++ 代码风格、LLVM 代码风格等。尽量向好的代码看齐。也为此进行过多次重构。第二,接触到了许多工具,比如 git,camke,ci 等。刚开始我也不会用 github 的,在本地写了,然后在网页上打开去一个一个的修改。后来我才知道这是个多么蠢的姿势…然后是接触到 cmake,我承认我到现在也不完全会用,但已经比之前好多了…还有 ci,也是最开始根本不会用,文档看不懂,折腾了很久,到现在勉强可以使用。第三,了解到很多概念。比如跨平台跨编译器,编译器版本,编译参数等等非常多的东西。为了能测试在其它平台上的运行,还折腾了一段时间双系统,linux 什么的。以前只会在 VS 里面 Ctrl + F5,现在至少可以用 g++ test.cpp -O2 -std=c++11 -Wall -Wextra -Wno-sign-compare 这类的东西而且明确的知道每个参数的意义。
所以,这期间其实是很长的一段时间,不一定都是因为我做这个我才了解,也有可能做别的我也能了解,但有些我知道了我就有一个东西让我去用上。因此我认为我造一个 tinystl 就是有用的。因为它培养了我很多良好的工程习惯,让我后面在写各种东西的时候,能够比较轻松自如,知道怎么去着手。
所以如果你已经有过这些经验,你去造一个 tinystl,你可能不会像我一样收获那么多,但如果你想做,那就做呗,反正标准库又不好用(逃

总结

总结其实都在上面的话里,让我再总结一遍,那就几句话吧:

  • 给个 star
  • 我不懂模板,想搞清楚 STL 的实现,所以就做了
  • 如果你想做,你需要了解过 STL,使用过 STL,并对其中的数据结构的原理非常熟悉
  • “抄袭” 很正常啊
  • 你觉得有用就有用,你想做就做,管别人怎么说干嘛
-------------------------------- 全文完 感谢您的阅读 --------------------------------
「写的那么辛苦,连一块钱都不打赏吗/(ㄒoㄒ)/~~」