[2] 智能指针

【1】什么是智能指针?

一句话定义:智能指针是一个行为与指针相同且会自动管理堆内存的模板类对象。

对定义进行简单解析:

1.1 “行为与指针相同”:满足使用起来类似于指针一样的用户体验感

1.2 “会自动管理”:智能之核心体现

1.3 “堆内存”:注意必须是堆内存,这里主要针对栈内存而论。

1.4 “模板类”:一种智能指针不能只对某种类型智能,而是要对所有数据类型都智能,即兼容所有数据类型,不用顾虑数据类型差异。

1.5 “对象”:面向对象的关键。指针实际是一个变量,而智能指针本质上是一个对象。

【2】为什么需要智能指针?

一个类库的产生势必有其被触发的动机,正如某句经典语所云:在这个世界上,没有无缘无故的爱,也没有无缘无故的恨。

同样的,在C++世界里,也不是无缘无故的出现智能指针。

搞清楚智能指针的设计动机,对于正确的理解智能指针工作原理、设计思想、以及适用场合是有着非常直接的帮助。

所以,让我们先来看一看:为什么需要智能指针?

从本质上来说,智能指针的引入实际上是为了解决一个问题:

为了有效准确地管理程序运行过程中动态申请的内存资源,以妥善地解决动态申请到的内存的拥有权问题。

(为了便于描述,下文中将智能指针所要解决的问题简述为“动态内存拥有权问题”)

 

在C++程序中,我们经常会出于这样或那样的原因,需要从系统堆(heap)中动态申请内存,用于存放一些只有在程序运行期间才能确定其大小的数据对象。

而在现实的计算机系统中,内存资源总量是很有限的,因此有了申请,就得要有释放,只有那样,才能好借好还,为应用程序可以从heap中持续申请获得内存做好铺垫。

 

如果,对于每一个动态内存申请点来说,我们都能够清晰地明确其释放点,那么,动态申请内存的释放似乎并不是一件值得特殊考虑的事情。

但是,实际软件系统中的数据依赖关系往往非常复杂,很多时候,不太容易确定一块动态申请到的内存到底到何时才真的可以允许释放掉。

比如说,对于同一块动态申请到的内存,在程序中往往会有多个引用点,这种多指一的关系,就提出了额外的数据一致性的要求,

只有在确保多个引用点都不会再访问这块动态内存的前提下,才可以安全地将其释放掉。

再比如说,C++中的异常处理机制强化了程序员处理错误场景的能力,但是异常这种机制在本质上类似于goto语句的灵活性也为动态内存的释放增加了管理上的负担。

当然,为了精准无误的手工释放动态申请到的内存,程序员可以仔细地对程序中的数据依赖关系作仔细地考量和设计,从而确保安全准确地手工释放动态内存。

但是,动态内存的拥有权问题往往并不是软件系统的核心竞争力所在(我们可以说内存管理是一个软件系统的基础设施的重要构成部分,但说它是一个软件系统的核心竞争力所在,

似乎就不太合适了,当然,对于以内存管理为市场卖点的软件产品是个例外,比如SmartHeap),

在软件开发过程中,花费过多精力来解决动态内存的拥有权问题,看起来多少有些不划算。

如果能够将“动态内存拥有权”这个问题的复杂性,从软件产品本身的复杂性中分离出来,由专门的人或团队来负责实现,实际上是非常有利于软件的模块化和复用性的。

毕竟,从本质上来看,动态内存的拥有权和一款软件产品本身所要解决的目标问题在相当大程度上是正交的,将其分解开来,分而治之从软件工程学的角度来看实在是个不赖的选择。

所以说,智能指针正是为了专门解决“动态内存拥有权”这一问题的产物。

真理:道理都不是看,读或者听懂的!

【3】智能指针怎样解决了“动态内存拥有权”问题?

那么,具体来说,智能指针以什么样的方式解决了"动态内存拥有权"的问题呢?

概要地来说,智能指针通过增加一个封装类,让指向动态内存的指针在封装类的对象层面具备了值语义(关于值语义这个概念,请参见随笔《值语义与对象语义》),从而解决了指针所指向的动态内存的拥有权问题。

 

进一步深入探讨,我们发现动态内存指针之所以不满足值语义,是因为动态内存指针涉及到其指向的动态内存的拥有权的管理,而在将一个动态内存指针所指向的动态内存地址复制到目标指针的同时,

指针所指向的动态内存的拥有权也被该目标指针共享,对同一片动态内存区域,存在两个引用点,这就带来了同步性的问题,对源指针和目标指针的操作不是完全正交的。

 

从本质上来说,给定一块动态内存区域,如果存在多个指向它的对象,动态内存拥有权的同步问题是不可回避的。

不过,我们是不是可以将这个拥有权的同步问题封装在有限的范围,从而在对象接口层面让客户端的代码获得值语义的使用体验呢?这正是智能指针的设计理念。

 

通常来说,智能指针会提供一个封装类,对C++中原生的指针进行封装,在封装类内部,通过一个计数器来协调指向同一块动态内存区域的多个智能指针之间的所有权关系。

而封装类会提供客户端代码通常会施加在原生指针上的绝大多数操作接口,比如提领操作,地址比较操作,等等。

这样,在保证跟原生指针相近的操作体验的同时,简化了动态内存的管理负担。

 

注:针对不同的应用场景,实际的智能指针也存在若干不同的分类,比如:

1> 支持毁坏式复制语义及RAII的智能指针,典型代表是std::auto_ptr(请参见随笔《 [3]智能指针std::auto_ptr 》);

2> 不支持值语义,只提供RAII的智能指针,典型代表是boost::scope_ptr(请参见随笔《 [4]智能指针boost::scoped_ptr 》),其类似于STL中的unique_ptr智能指针;

3> 支持值语义,提供引用计数机制及RAII支持的智能指针,典型代表是boost::shared_ptr(请参见随笔《 [5]智能指针boost::shared_ptr 》),其类似于STL中的shared_ptr智能指针;

4> 为了解决指针循环引用而引入的智能指针,典型代表是boost::weak_ptr(请参见随笔《 [6]智能指针boost::weak_ptr 》);

 

Good Good Study, Day Day Up.

顺序  选择  循环  总结

posted @ 2013-08-28 09:58  kaizenly  阅读(399)  评论(0编辑  收藏  举报
打赏