智能指针总结

文章转自: http://www.zhihu.com/question/20368881       

                                                                                       智能指针总结

        智能指针和普通指针的区别在于智能指针实际上是对普通指针加了一层封装机制,这样的一层封装机制的目的是为了使得智能指针可以方便的管理一个对象的生命期。

在C++中,我们知道,如果使用普通指针来创建一个指向某个对象的指针,那么在使用完这个对象之后我们需要自己删除它,例如:

ObjectType* temp_ptr = new ObjectType();
	temp_ptr->foo();
	delete temp_ptr;

      很多材料上都会指出说如果程序员忘记在调用完temp_ptr之后删除temp_ptr,那么会造成一个悬挂指针(dangling pointer),也就是说这个指针现在指向的内存区域其内容程序员无法把握和控制,也可能非常容易造成内存泄漏。

      可是事实上,不止是“忘记”,在上述的这一段程序中,如果foo()在运行时抛出异常,那么temp_ptr所指向的对象仍然不会被安全删除。

       在这个时候,智能指针的出现实际上就是为了可以方便的控制对象的生命期,在智能指针中,一个对象什么时候和在什么条件下要被析构或者是删除是受智能指针本身决定的,用户并不需要管理。

       根据具体的条件,我们一般会讨论这样几种智能指针,而如下所说的这些智能指针也都是在boost library里面定义的
1) scoped_ptr:

      这是比较简单的一种智能指针,正如其名字所述,scoped_ptr所指向的对象在作用域之外会自动得到析构。此外,scoped_ptr是non-copyable的,也就是说你不能去尝试复制一个scoped_ptr的内容到另外一个scoped_ptr中,这也是为了防止错误的多次析构同一个指针所指向的对象。
2) shared_ptr:

       很多人理解的智能指针其实是shared_ptr这个范畴。shared_ptr中所实现的本质是引用计数(reference counting),也就是说shared_ptr是支持复制的,复制一个shared_ptr的本质是对这个智能指针的引用次数加1,而当这个智能指针的引用次数降低到0的时候,该对象自动被析构,这一点两位同学给的答案都非常精彩,不再赘述。需要特别指出的是,如果shared_ptr所表征的引用关系中出现一个环,那么环上所述对象的引用次数都肯定不可能减为0那么也就不会被删除,为了解决这个问题引入了weak_ptr。
3) weak_ptr:

      对weak_ptr起的作用,很多人有自己不同的理解,我理解的weak_ptr和shared_ptr的最大区别在于weak_ptr在指向一个对象的时候不会增加其引用计数,因此你可以用weak_ptr去指向一个对象并且在weak_ptr仍然指向这个对象的时候析构它,此时你再访问weak_ptr的时候,weak_ptr其实返回的会是一个空的shared_ptr。实际上,通常shared_ptr内部实现的时候维护的就不是一个引用计数,而是两个引用计数,一个表示strong reference,也就是用shared_ptr进行复制的时候进行的计数,一个是weak reference,也就是用weak_ptr进行复制的时候的计数。weak_ptr本身并不会增加strong reference的值,而strong reference降低到0,对象被自动析构。为什么要采取weak_ptr来解决刚才所述的环状引用的问题呢?需要注意的是环状引用的本质矛盾是不能通过任何程序设计语言的方式来打破的,为了解决环状引用,第一步首先得打破环,也就是得告诉C++,这个环上哪一个引用是最弱的,是可以被打破的,因此在一个环上只要把原来的某一个shared_ptr改成weak_ptr,实质上这个环就可以被打破了,原有的环状引用带来的无法析构的问题也就随之得到了解决。
4) intrusive_ptr:

      简单的说,intrusive_ptr和shared_ptr的区别在于intrusive_ptr要求其所指向的对象本身实现一个引用计数机制,也就是说当对象本身包含一个reference counter的时候,可以使用intrusive_ptr。


另解:

(1)智能指针用于内存管理,主要是用于对堆上面开辟的内存的管理,具体采用引用计数的机制进行。比如我们在栈上开辟了一块内存m1,并将其赋值给指针p1,那么现在m1这块内存就有一个对象在使用,引用计数为1。这时如果有另外一个指针p2也需要使用m1的内容,那么就将p2也指向m1。问题在于,如果p1使用完毕之后,使用delete语句告诉系统,这块内存我不用了,把它回收吧,那么这时p2还在指着m1的话,再次使用p2的时候就会出问题了。然后就引入了引用计数的概念。所有的栈上的内存,在还没有被开辟的时候,该块内存的引用计数为0,在第一次用p1开辟的时候引用计数+1变成1,如果有其他指针也需要这块内存,比如一个潜copy操作,比如p2,那么这时候就有两个指针指向m1,引用计数变成2,当p1用完了,就用一个操作切断p1和m1的关系,m1的引用计数变成1。当p2也用完了,那么通过一个操作引用计数再次减去1,引用计数变成0。当智能指针这个对象发现它管理的内存引用计数变成0的时候,对m1做一个delete操作,使之释放。

(2)智能指针最基本的概念是引用计数,也就是智能指针内部有一个计数器,记录了当前内存资源到底有多少指针在引用(可以访问这个资源),当新增加一个可以访问这个资源的引用时,计数器会加1,反之会减去1,当计数器为0时,智能指针会自动释放他所管理的资源。手动申请,自动释放,就是其智能的体现。
例如:

int main()
{
	{ 	// 局部作用域
		// 引用计数:1,也就是只有一个指针p引用(访问)这块int内存
		shared_ptr<int> p(new int);
			{// 引用计数:2,指针p和copy两个指针可以访问这块内存
				shared_ptr<int> copy = p;
			}// 引用计数:1,超出copy的作用域,只有p可以访问这块内存
	} // 引用计数:0,超出p的作用域,没有指针可以访问这块内存, 资源被自动释放
	return 0;
}

(3)智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露。它的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。智能指针就是模拟指针动作的类。所有的智能指针都会重载 -> 和* 操作符。智能指针还有许多其他功能,比较有用的是自动销毁。这主要是利用栈对象的有限作用域以及临时对象(有限作用域实现)析构函数释放内存。当然,智能指针还不止这些,还包括复制时可以修改源对象等。智能指针根据需求不同,设计也不同(写时复制,赋值即释放对象拥有权限、引用计数等,控制权转移等)。auto_ptr 即是一种常见的智能指针。

智能指针通常用类模板实现:

大多数C++类用三种方法之一管理指针成员

1、不管指针成员。复制时只复制指针,不复制指针指向的对象。当其中一个指针把其指向的对象的空间释放后,其它指针都成了悬浮指针。这是一种极端

2、当复制的时候,即复制指针,也复制指针指向的对象。这样可能造成空间的浪费。因为指针指向的对象的复制不一定是必要的。

3、第三种就是一种折中的方式。利用一个辅助类来管理指针的复制。原来的类中有一个指针指向辅助类,辅助类的数据成员是一个计数器和一个指针(指向原来的)(此为本次智能指针实现方式)。

其实,智能指针的引用计数类似于java的垃圾回收机制:java的垃圾的判定很简答,如果一个对象没有引用所指,那么该对象为垃圾。系统就可以回收了。

(4)在C++中,我们通过new(在动态内存中为对象分配空间并初始化对象)和delete(销毁该对象,并释放内存)直接分配和释放动态内存。

如下代码:

int *pi = new int;//pi 指向一个未初始化的int

有些人有这样的疑问,指针一定要new吗?其实指针和new没有什么关系。这里的new在动态内存里为对象分配了内存空间,并返回了一个指向该对象的指针。new是申请堆空间,不是在栈上分配内存,指针只要指向有效的内存空间就可以。比如:

int i;

int *p =&i;      //p可以直接使用了

new直接初始化对象:

int *pi =new int(128);//pi指向值为128的对象

string *ps =new string("christian");//*ps 指向“christian”的字符串

new分配const对象必须进行初始化,并且返回的是一个指向const对象的指针:

const int *p= new const int(1024);//分配并初始化一个const int;

当然new申请内存分配的时候也不是都会成功的,一旦一个程序用光了他的所有可用内存(虽然这种情况一般很少发生),new表达式就会失败。这时候会抛出bad_alloc异常。所以我们需要通过delete来释放占用的内存。在这里注意delete并不是要删除指针,而是释放指针所指的内存。

int i;

int *pi =&i;

string str ="dwarves";

double *pd =new double(33);

delete str;// 错误:str不是一个指针

deletepi;  // 错误:pi指向一个局部变量

deletepd;  // 正确

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值