1.7 插曲:系统地避免各种指针错误
我同意孟岩说的9 “大部分用C 写的上规模的软件都存在一些内存方面的错误,需要花费大量的精力和时间把产品稳定下来。”举例来说,就像Nginx 这样成熟且广泛使用的C 语言产品都会不时暴露出低级的内存错误。
内存方面的问题在C++(www.cppentry.com) 里很容易解决,我第一次也是最后一次见到别人的代码里有内存泄漏是在2004 年实习那会儿,我自己写的C++(www.cppentry.com) 程序从来没有出现过内存方面的问题。
C++(www.cppentry.com) 里可能出现的内存问题大致有这么几个方面:
1. 缓冲区溢出(buffer overrun)。
2. 空悬指针/野指针。
3. 重复释放(double delete)。
4. 内存泄漏(memory leak)。
5. 不配对的new[]/delete。
6. 内存碎片(memory fragmentation)。
正确使用智能指针能很轻易地解决前面5 个问题,解决第6 个问题需要别的思路,我会在§9.2.1 和§A.1.8 探讨。
1. 缓冲区溢出:用std::vector<char>/std::string 或自己编写Buffer class 来
管理缓冲区,自动记住用缓冲区的长度,并通过成员函数而不是裸指针来修改缓冲区。
2. 空悬指针/野指针:用shared_ptr/weak_ptr,这正是本章的主题。
3. 重复释放:用scoped_ptr,只在对象析构的时候释放一次。
4. 内存泄漏:用scoped_ptr,对象析构的时候自动释放内存。
5. 不配对的new[]/delete:把new[] 统统替换为std::vector/scoped_array。
正确使用上面提到的这几种智能指针并不难,其难度大概比学习使用std::
vector/std::list 这些标准库组件还要小,与std::string 差不多,只要花一周的时间去适应它,就能信手拈来。我认为,在现代的C++(www.cppentry.com) 程序中一般不会出现delete语句,资源(包括复杂对象本身)都是通过对象(智能指针或容器)来管理的,不需要程序员还为此操心。
在这几种错误里边,内存泄漏相对危害性较小,因为它只是借了东西不归还,程序功能在一段时间内还算正常。其他如缓冲区溢出或重复释放等致命错误可能会造成安全性(security 和data safety)方面的严重后果。
需要注意一点:scoped_ptr/shared_ptr/weak_ptr 都是值语意,要么是栈上对象,或是其他对象的直接数据成员,或是标准库容器里的元素。几乎不会有下面这种用法:
- shared_ptr<Foo>* pFoo = new shared_ptr<Foo>(new Foo); // WRONG semantic
还要注意,如果这几种智能指针是对象x 的数据成员,而它的模板参数T 是个incomplete 类型,那么x 的析构函数不能是默认的或内联的,必须在.cpp 文件里边显式定义,否则会有编译错或运行错(原因见§10.3.2)。