本文介绍C++的四个智能指针: auto_ptr, unique_ptr, shared_ptr, weak_ptr 其中后三个是c++11支持,并且第一个已经被C++11弃用。
智能指针和普通指针的区别在于智能指针实际上是对普通指针加了一层封装机制,这样的一层封装机制的目的是为了使得智能指针可以方便的管理一个对象的生命期。
auto_ptr
- 出现时间最早(C++98),现在(从C++11开始)已被弃用
- 采用所有权模式,但是限制不足
- 不能指向对象数组,其析构函数内使用的是delete而非delete[]
- 不能作为STL标准容器的元素
智能指针出现的思想是使用类来管理资源,无须手动释放资源,减少内存泄露的机会。auto_ptr完成了这一目的。举例:
1 |
|
但是auto_ptr对资源所有权的剥夺较为隐晦,从而又导致其它问题,即内存访问出错。举例:
1 | void remodel_auto_ptr (std::string & str) |
以至于后来不得不对auto_ptr
重新设计,从而产生了unique_ptr
、shared_ptr
以及weak_ptr
。unique_ptr
和shared_ptr
分别从独占和共享角度出发对auto_ptr
改进,规定了上文代码中ps2 = ps
的行为。weak_ptr
设计的目的主要用于解决shared_ptr
循环引用问题。
unique_ptr
- 用以代替auto_ptr
- 采用所有权模式,严格限制
- 临时右值允许转移所有权
unique_ptr
直接不允许上文代码中ps2 = ps
的赋值行为,从而避免auto_ptr
情况下ps
访问错误。但有时确实有使ps2和ps同时能访问某一对象的需求,所以又设计了shared_ptr
shared_ptr
- 采用共享所有权模式指向对象
- 使用
引用计数
机制管理对象被释放的时机
Notes: 指针是指向对象还是引用对象?引用计数
机制管理指针指向对象的个数,所以用引用似乎没有问题,但使用引用
这个词又怕与引用类型混淆)
多个shared_ptr
可以同时指向一个对象,当最后一个shared_ptr
离开作用域时,对象内存才会自动释放。 似乎使用unique_ptr
和shared_ptr
非常完美,满足各种需求。但实际上总会出现这样一个问题:每引入一种机制解决现有问题,而又不可避免的带来其它问题。但是代价不一样。shared_ptr
会产生循环引用问题,但这种问题发生的频率相比于使用auto_ptr的问题很低。
智能指针的循环引用概念比资源的独占或共享概念较复杂,举例说明之:
1 |
|
上面A类中包含一个shared_ptr,B类也包含一个shared_ptr。A对象有sa以及sb->pa指向,引用数为2。A对象有sb以及sa->pb指向,引用数也为2。当离开main函数作用域时,sa和sb要被销毁,A和B对象各自引用数减1,由于引用数不为0,均无法被sa->pb和sb->pa删除,造成A、B对象均未被回收,内存泄露。
由此归纳出循环引用产生条件:
- 至少含两个对象,且其中均为智能指针(强引用指针)
- 对象中的智能指针同时指向了这些对象,它们之间的指向构成一个环。
打破循环引用的关键在于使环中任一对象的引用数为0,这要求某些对象相互引用时的引用数不能够增加。有如下方法(这些方法是或的关系,选其一即可):
- 将生命周期最短的类,其中的指针设计为普通指针
- 将类中的指针设计为弱引用智能指针
上面方法,仅仅是针对必须使用循环引用的情况。大部分情况是我们不小心造成了循环引用,此时修改引用逻辑即可。
weak_ptr
- 使用weak_ptr引用对象不增加引用数,因此称之为弱引用。
- 不能直接通过weak_ptr来访问资源。 在需要访问资源的时候weak_ptr为你生成一个shared_ptr,shared_ptr能够保证在shared_ptr没有被释放之前,其所管理的资源是不会被释放的。创建shared_ptr的方法是lock()方法。
- expired()方法用于检测所管理的对象是否已经释放。如果已经被释放,获取到的是一个空的shared_ptr(NULL)。
计划更新
- weak_ptr
- unique_ptr临时允许右值转移所有权
- shared_ptr使用举例
- unique_ptr使用举例