读书札记之 —— 《C++ 标准库(第二版)》

呼……这本书总算是看完了,前前后后看这本书大概花了二十来天,总算看完了(更准确的 说是翻完了)这本砖头一样厚(还是块加厚版的砖)的书。

你要是问我记住了些什么,我只能说啥也没记住,C++标准库的内容终究还是太多,细节太 多,能装在脑子里的东西真的很少。其实我在看这本书之前就知道会是这个结果,所以也 没有觉得自己白读了这本书,留在脑海中不是也不应该是具体的细节,而是关于标准库的 一些感悟,前面读这本书的时候陆陆续续也有写一些文章来记录这些东西。

我为什么读这本书

这本书是关于C++标准库的一个权威的指南,其实读这本书的原因和学标准库的原因是一样 的。目前的任何一门语言都是语言的核心+语言的标准库,没有掌握标准库之前,我们都 不能说自己掌握了这门语言。我希望自己能够掌握这门语言,所以我在了解了基本的语法 之后读了这本书。如果你想掌握这门语言,我也推荐你看这本书。

我曾经在知乎上看到过一个观点:

一门语言要么是语言核心语法难,要么是它的库很难

对于C++来说,是这两者都很难,C++之所以会这么难学原因估计就在于此。

最大的体会

读完这本书,突然发现其实自己对于C++的了解非常的有限。过去的自己对于C++的理解仅 仅停留在C++ OOP的角度,C++标准库让我体会到C++范型编程的强大之处。我自己对于 范型只停留在会用(甚至都不太会用)的级别,自己动手进行范型编程的能力基本为零, 而这种能力的缺失可以说让我丧失了C++威力的半壁江山。

OOP的世界里,我们一直提倡面向接口编程,而对于C++来说,类型就是接口,所以整 个系统都在和类型打交道,强大的静态类型系统也被认为是C++的一项法宝。当然C++并没 有把接口和实现区分的非常清楚[^1],这也是很多人指责C++的一个原因。

范型编程完全是另外一种思维的存在,范型的关键思维在于像什么就是什么而不去在乎 它真正到底是什么。所以指针可以当成迭代器用,函数指针和函数对象在范型角度看来并 没有区别,类型在范型编程中并没有太大的作用。这种思维的背后的逻辑才是真正纯粹的 面向接口编程,只要你提供正确的接口,你就可以正确的运行,至于你到底是什么类型对 于我来说根本不重要。从这个角度来说,Python编程就是范型编程,因为Python中你 只需要确定一个对象有某个接口,至于对象的类型你可以完全不管。

C++标准库中的核心武器:STL的设计就是这种逻辑,我渐渐的明白,为什么很多大师级 的人物更倾向于使用namespace中定义普通函数而不是类的内部定义成员函数。那些嘲笑 C++并自诩完全面向对象的Java程序员是多么的可笑。

这些理解不一定正确,它是我看完STL迭代器和算法之后最大的一种体会,其实范型真的不 仅仅是把容器类型参数化而已。

最容易忽视的东西

对我来说,整个库中最容易被我忽视的东西是STL算法,其实其中的算法包罗万象,远远 超过我原来了解的范围。也许我们不应该使用:

1
2
3
for (auto beg = v.begin(); beg != v.end(); ++beg) {
// do something
}

而是考虑:

1
2
3
for_each(v.begin(), v.end(), [] (const value_type& elem) {
// do something
});

我们经常需要考虑如何在一次 for 循环中删除掉某个元素而保证 iterator 有效,其 实我们应该考虑能否直接用 remove_if 来完成这一功能。

这样的例子很多,我们一直重复的一些动作其实STL中早就为我们封装好了算法,只不过 在没有发现之前,我们总是在不断的重复着那些我们自以为很优雅的解法。

最容易自以为懂了的东西

其实这本书中的大部分内容以前都不太全面的接触过:智能指针STL字符串I/O正则表达式线程 等等。我自以为自己懂了却发现完全不懂的东西是I/O这 一部分。

1
cout << "hello world" << endl;

这样的语句大家都会用,但是我从来没有去考虑过所谓的 endl 到底是什么东西。后来 我知道 endl 是一种操控器,自以为自己懂了,却从来没有想过

1
cout << endl;

为什么可以换行,并把内容显示在屏幕上。看完这本书之后我才知道其实 endl 是一个 函数,它接收一个 stream 作为它的参数。伪代码去下:

1
2
3
4
5
ostream& endl(ostream& os) {
os.put('\n');
os.flush();
return os;
}

ostream<< 操作符有一个重载版本,接收一个函数指针作为它的参数:

1
2
3
ostream& ostream::operator << (ostream& (*op) (ostream&)) {
return (*op)(*this);
}

就是是为什么 cout << endl; 可以正常工作的原因。你完全可以扩展自己的“操控器”来 完成自己额外的需求。

I/O 其实是一个设计非常好的库,它的各大组件之间的职责分派非常明确,stream 其实自身根本不处理实际的输入输出,这些东西都委托给了 streambuf 处理。I/O 本 身可能在项目中使用的并不是特别的多,但是个人觉得它的源码库有机会还是很值得读一 读的。

黑魔法

C++11有许多新的特性,比如:变参模板,右值引用等等。这些特性造就了一堆的黑魔法出 现,比如tuplebinderfunction。很遗憾这本书并没有深入的讲解这些黑魔法的 具体实现机制,只是提到了它们的用法。关于 tuple 的实现,《深入理解C++11:C++11 新特性解析与应用》这本书中有比较详细的介绍。关于 binder 看过一篇相关的文章 图解boost::bind,感觉还不错。