Boost库学习笔记(一):智能指针和内存池

一、前言

  这篇是这个系列的开篇第一作,所以还是想简单说点题外话。
  以前上学的时候学过C++,所谓经典的《 C++ Premier Plus》、《 C++ Premier》、《C++ Templates》三本书也都看过,那些诡异的语法、隐晦的潜规则也曾啃过,但是到现在基本都忘了。我想,究其原因:一方面是C++实在是过于的复杂,复杂到据说,当时一个公司的团队,打算用C++新开发一个项目,到后来整个团队跑题去研究C++语言本身了;另一方面如同自己之前所说的,缺少C++的项目经验,教科书基本就是一个点一个例子零散的教你,没有像样的大型项目训练很难深刻理解和掌握之。刚好前段时间对异步研究的比较多,然后沿着boost::asio,了解到boost C++开发库,比较出名而且在正式项目中用的比较多,觉得以此为切入点还是不错的。
  还有,至于为啥要干C++。虽然现在Python、go这些语言火的如日中天,但觉得后台底层开发基本还是C,C++,Java三分天下的局面:Java在中间件中用的比较多,而且Apache旗下一大堆的分布式组件和算法库也都是Java实现的,可谓前途广阔;C作为现代编程语言的鼻祖,Linux内核项目证明了C完全是可以开发大型项目的,但是之前自己写的几个小程序,感觉C的确也是年世已高了,语法简洁但是缺乏上层抽象,很多东西需要自己造轮子,或者不断的选库集成;C++做后台是很多的,比如自己以前实习的移动通信核心网,就是C++写的,C++的模板特性,再加上C++标准不断的吸纳新的数据结构、算法等内容,让C++不断地被丰富完善,以适应现代大型项目快速开发的需求。从个人感觉上来说,Java开发最省心,这也是GC(垃圾回收)类语言的最大优势,但是不是说Java性能慢么?C语言简洁优雅而又直白,给定一个代码,可以很明确的分析其预测结果,但是正因为简洁优雅,很多高级编程的抽象需要底层去实现,开发效率算是最低吧。而C++估计是最复杂的语言了,隐含的规则很多,常常代码需要推理一下才知道可能的结果,而且如果对C++了解不深入的话,感觉更容易照成内存泄漏,其远没有C的malloc/free这么简单!
  关于boost的历史缘由,以及跟C++标准的关系,这里就不再整理了,网上一搜全部都是。可以说boost库是C++新特性的实验基地吧,在boost中比较好的库,很有可能会成为后续C++的标准,所以boost也会被冠以“C++准标准”的美誉。
Boost
  刚买了一本新的《C++ Premier》第五版,书中着重强调是一本依据C++11标准更新改写的,初步瞭阅一下,感觉不错,希望C++11和boost能给我带来新的成长。

二、Boost库的代码

2.1 下载代码

  现在C++库的趋势,就是库以头文件形式直接包含使用的,而boost中大部分库都是这种德行,除非少有的几个库需要额外的编译操作。如果想深入学习研究,不如拷贝一份源码下来,没事的时候拉拉上游的更新,有心情的时候看看源码,看看大神级别的代码是怎么练成的。

1
user@gentoo ~ % git clone --recursive https://github.com/boostorg/boost.git

  这个库比较的大,即使用tar.bz2打包之后,都有500多兆,毕竟有一百多个库,还有这么久的提交历史。不能忍受国内龟速的我,是在墙外的VPS上clone下来,然后打包下载下来的。然后就可以用SlickEdit建立项目tag代码了。后面可以随时更新代码:

1
2
user@gentoo boost % git pull
user@gentoo boost % git submodule update --recursive

2.2 编译一下

  编译的好处,一方面确保最上游的代码是可用的,否则可以提交一些issue或者pull request,也可以为社区奉献自己的一份力;再则就是后续开发应用程序,链接到自己编译的库,调试的时候可以跟踪到库内部调试,可以在调试程序的同时,增加你对boost库的深入理解和认识。

1
2
user@gentoo boost % ./bootstrap.sh
user@gentoo boost % ./b2 --ignore-site-config --build-dir=builddir/ --prefix=installdir/ -a toolset=gcc variant=debug threading=multi install

  上面的第一步,就是生成boost的编译工具b2,而第二步才是boost库的真正编译。完工之后,会在./installdir目录生成include和libs结果,怎么用就不用说了吧。

三、智能指针

  前面废话了这么多,本文的主题来了。智能指针(smart_ptr)定义在boost/smart_ptr当中,主要是用于解决heap堆内存动态对象生命周期(传统的C++的堆对象需要使用new/delete来创建和释放,这就需要在离开作用域以及catch捕获异常的时候,正确的调用delete释放对象),以及对象被多个所有者共享之问题,属于C++内存管理之范畴。boost包含的智能指针有如下类型(在C++11已经引入了shared_ptr和weak_ptr以及unique_ptr智能指针了)

指针 特性
scoped_ptr Simple sole ownership of single objects. Noncopyable.
scoped_array Simple sole ownership of arrays. Noncopyable.
shared_ptr Object ownership shared among multiple pointers.
shared_array Array ownership shared among multiple pointers.
weak_ptr Non-owning observers of an object owned by shared_ptr.
intrusive_ptr 请不要用它!

3.1 std::auto_ptr

  std::auto_ptr是C98标准中的,其目的就是代理原始对象的指针,当该变量离开作用域(以及发生异常)的时候,就会调用delete删除原始对象,保证原始对象得到正确的析构和释放。但是这个std::auto_ptr定义的比较宽泛,使用的时候需要注意一下的缺陷:

  • 多个std::auto_ptr不能同时管理同一个对象,否则在离开作用域的时候会发生多次析构,行为是不确定的;
  • 其析构的时候调用的是delete而非delete[],所以不能处理数组类型的指针;
  • std::auto_ptr设计的初忠就是可被转移的,但是在任何一个时刻,只能有一个智能指针获得对象的控制权,意味着当别的智能指针获得std::auto_ptr所管理对象的控制权的时候,这个std::auto_ptr同时就失去了控制权(==0)了;
  • 不能放入容器中,因为任何的赋值将会导致控制权的转移;
  • 可作为函数的返回值;

3.2 boost::scoped_ptr

  能够保证被包装的任何对象都能够在离开作用域的时候被正确的析构,但是对所有权也更为的严格——一旦boost::scoped_ptr获得了对象的所有权,将不能再取回来,通过将拷贝构造函数和赋值操作符private私有,保证其管理的指针不会被复制或者转让,不容易被误用。

  • 重载了->和*操作符,可以像普通指针一样使用;
  • 不能重载==和!=操作符,可以在bool上下文中检查其管理的指针是空指针还是有效指针;
  • reset(T* p)会删除原来保存的指针,然后赋值为p;
  • get()会返回原始指针,但是不能手动delete这个指针,因为在脱离作用域的时候,boost::scoped_ptr还是会再次发生析构操作的;
  • 无法在标准容器中存放boost::scoped_ptr,因为这种类型的指针不支持拷贝和赋值;

3.3 boost::scoped_array

  同boost::scoped_ptr类似,只不过是用来管理new[]数组类型的对象的,没有->和*操作符,而是重载了operator []操作符,可以索引数组元素。
  由于不提供指针运算,所以也无法使用(数组首地址+元素偏移)这样的访问方法;同时不会检查索引的合法性,不能动态增加长度,不能迭代,所以没别的理由,推荐使用vector

3.4 boost::shared_ptr

  基于引用计数型的智能指针,可以被自由地拷贝和赋值,也可以被放入到容器中。只有当引用计数为0的时候,才会安全的析构对象。

  • 提供*、->、bool类型的操作,get()可以获得原始指针;
  • 提供reset()函数,调用时候计数值-1同时不再持有对资源的管理(内部指针为0),但计数值不为0的时候,是不会产生析构操作的;当reset()带参数的时候,-1后会修改内部指针管理其它资源了;
  • unique(),检测use_count==1;
  • 提供==和<比较,等同于a.get()和b.get()的比较;提供<<操作符可以输出内部的指针值;
  • 不能使用a.get()获得指针后再用标准C++的类型转换,而应该使用static_pointer_cast、const_pointer_cast、dynamic_pointer_cast这类的转换接口;
  • 【工厂模式】理想情况下智能指针管理下完全不必要使用delete了,而在堆上创建对象除了使用new,还可以使用工厂模式——boost::make_shared,其参数将被传递给给模板类的构造函数,返回shared_ptr对象。

3.5 boost::shared_array

  基本同上。
  同样的,推荐使用boost::shared_ptr或者std::vector。对于后一种方式,虽然容器也可以直接存储原始指针,但是不能保证原始对象的析构操作,而用boost::shared_ptr则可以通过引用计数的原理正确的管理对象。

3.6 boost::weak_ptr

  本身不会被单独拿来用,通常是和boost::shared_ptr协同作为静态观测者使用的,其不会改变对象的引用计数,本身不具备指针的任何基本功能(比如*和->)。

  • boost::weak_ptr在构造和析构的时候,不会引起boost::shared_ptr引用计数的变化;
  • use_count()成员函数返回观测资源的引用计数,而expired()等同于检测use_count()==0,表示观测资源已经不复存在;
  • boost::weak_ptr构造的时候,使用boost::shared_ptr或者其它的boost::weak_ptr进行初始化,然后调用lock()方法,如果对象存在,那么会返回一个boost::shared_ptr(同时原始资源的计数会+1),否则就返回0;
  • C++中this和boost::shared_ptr一个惯用,就是让自己的类继承enable_shared_from_this,然后就可以使用make_shared工厂模式产生对象,而当需要boost::shared_ptr指针的时候,使用shared_from_this()就可以返回。关于这点,还想说的是,在使用boost::shared_ptr的时候,为了保证对象正确被析构,需要遵循以下法则:(1)在一个对象的内部,不要去利用boost::shared_ptr持有对象自身;(2)当两个对象互为对方成员变量的情况,必须使用弱引用;(3)当boost:;shared_ptr和boost::asio互用的时候,还有其它“槽点”,到时候再讨论吧。

3.7 std::unique_ptr

  这个是C++11中的,跟boost::scoped_ptr比较类似,也是不能拷贝和赋值的,任何时候只能有一个std::unique_ptr控制对象。但是其提供了一个转移语义:

  • release()会释放资源的控制权,同时返回指针。因此std::unique_ptr p2(p1.release())就实现了控制权的转移。大多数情况下,人们推荐const std::unique_ptr代替boost::scoped_ptr。

四、内存池pool类

  定义在boost/pool当中,主要包括pool、object_pool、singleton_pool和pool_alloc四个类型。

4.1 boost::pool

  最简单的类型,但也只能申请int/double等这些简单的数据类型,构造器接收的参数是申请单个内存块的尺寸大小。该类不会抛出异常。

  • malloc()、ordered_malloc()用于申请内存,free()可以手动释放内存,而内存池对象离开作用域的时候会自动释放所有的内存;
  • is_from()用于判断内存块是否是从这个pool内存池对象分配出去的;
  • release_memory()让内存池释放所有没有分配的内存,而已经分配的内存不受影响;purge_memory()强制释放所有的内存(等同于内存池析构时候操作);

4.2 boost::object_pool

  基本同boost::pool,只是在析构的时候会自动对所有分配的内存块调用析构函数。该类不会抛出异常。

  • 仍然提供malloc()和free()接口,但是这只是裸分配和释放内存,不推荐使用;
  • is_from()用于判断内存块是否是从这个pool内存池对象分配出去的;
  • construct()和destroy()接口,会首先调用malloc()/free()申请/释放内存,然后将调用参数传递给对应的构造函数/析构函数;

4.3 boost::singleton_pool

  接口类似boost::pool,但是是一个单件(设计模式中,单件确保只产生一个实例,但本身不是静态类),使用域操作符来调用静态成员函数,并且是线程安全的!
该内存池的内存会在整个程序运行的整个生命周期中一直存在,所以如果想在程序运行过程中回收内存,只能手动进行释放。

4.4 boost::pool_alloc

  不推荐使用,就不必多言了。

  附录是推荐的C++的学习教材。慢慢啃吧,会有好处的!

本文完!

参考