C++应用扫盲系列--智能指针篇

从今天起计划拟写关于C++总结的系列文章。随着学习的不断深入,越发感觉自己的实力之弱,以至于经常感觉自己像个C++盲……因此,我将这个系列的总结取名为C++扫盲。

 

如果要弄明白什么叫智能指针,首先需要了解智能指针究竟是怎么来的。

日常编程情景回顾

情景1共享所有权

 

Obj* fun(Obj *a)

{

Obj* b = a;

return b;

}

Obj i;

Obj* p1 = &i;

Obj* p2 = fun(p1);

p1->set(1);

delete p1;

……

p2->set(2);

 

在实际的编程当中,可能会遇到多个指针共享同一个内存区的情况。在上面的代码当中指针p1p2指向同一块内存区。这样这块内存何时释放才是合理的呢?这就要知道每一个引用这块内存区对象的生命周期以便确定调用delete合适的时间,而这样做无疑会增加程序的耦合度,使得程序的维护与升级变得更加复杂。如果稍不注意就会出现上面这段程序当中出现的问题--p1被释放之后,p2就变成了一个野指针。p2->set(2)这段代码还可以照常执行,只不过这段内存区域实际上是一个已经被程序释放掉的垃圾区域。这样的程序有时会正常的执行,但其中却存在极大的安全隐患,程序的执行结果无法预计。

 

情景2异常安全

while(1)

{

Obj*p = new Obj();

try

{

p->pop();

...

delete p;

}

catch(expection e)

{

...

cout<<"Field";

}

    ...

}

 

对于上述代码,动态的分配了一个Obj对象,如果在try模块当中出现了异常问题,那么在异常抛出时就不会执行delete p这条语句,即该对象的内存空间无法被删除。但是在本次while执行后,p所指向的内存区域可能会被丢失(提供了垃圾回收机制也不能保证资源不会被丢失)。这样犹豫内存的泄露,程序不仅可能耗尽系统的内存资源,也会对程序的运行状态产生影响。

 

情景3避免编程中不良习惯导致的错误

new与delete这一对关键字不是在所有程序员的代码中都会成对出现的。忘记执行delete可能会导致内存耗尽的问题。那么可不可以有一种机制来保证,在离开对象作用域之后,其所指向的内存区域就会被自动删除呢?

针对于上述的三个情景,程序员急切的需要安全与高效的智能指针来解决让人头疼的问题。

在C++标准库当中,提供了一种智能指针叫做auto_ptr。但是其功能有限,仅仅可以解决一小部分的问题。在C++boost库当中,提供了更加强大的Smart_ptr库,其中包括四种智能指针:scoped_ptr,shared_ptr,instrusive_ptr以及weak_ptr。

独霸资源的scoped_ptr

先说auto_ptr,它可以用来确保正确的删除动态分配的内存对象,这一点其使用方法与scoped_ptr相同。在程序离开作用域之后,即使程序员不显式的调用delete(如果使用,也不能调用delete),这个对象的内存区域也会被自动的解析掉。请看如下代码:

#include "boost/scoped_ptr.hpp"

#include <string>

#include <iostream>

int main() 

{  

boost::scoped_ptr<std::string>  

p(new std::string("Use scoped_ptr often."));  // 打印字符串的值  

if (p)  

std::cout << *p << '/n';     

// 获取字符串的大小

size_t i=p->size();  

// 给字符串赋新值  

*p="Acts just like a pointer";   

// 这里p被销毁,并删除std::string 

...

}

再来看一个auto_ptr的例子:

auto_ptr<Obj> p;

auto_ptr<Obj> another_p = p;

another_p->set(1);

p->set(2);

如果有人胆敢写出上述代码,那么程序难逃崩溃的命运。原因很简单,在执行了语句

auto_ptr<Obj> another_p = p;

之后,指针p就被自动析构掉了,变成了一个空指针。p把他的所有权转移给了another_p。但是这种行为会让很多程序员使用起来很不爽,这样才引发了scoped_ptr的诞生。

使用boost::scoped_ptr的时候必须注意,它不允许进行复制操作,一旦声明了一个指向某内存空间的指针,那么就不可以通过another_p = p;的方式来分配内存空间的新所有权。

 

号称最最智能的shared_ptr

shared_ptr号称是boost库当中最最智能的指针。它实现了一种对于资源共享对象的非侵入式计数机制。说到这里要先介绍两个概念:

侵入式

侵入式的引用计数管理要求资源对象本身维护引用计数,同时提供增减引用计数的管理接口。通常侵入式方案会提供配套的侵入式引用计数智能指针。该智能指针通过调用资源对象的引用计数管理接口来自动增减引用计数。COM对象与CComPtr便是侵入式引用计数的一个典型实例。

非侵入式

非侵入式的引用计数管理对资源对象本身没有任何要求,而是完全借助非侵入式引用计数智能指针在资源对象外部维护独立的引用计数。shared_ptr便是基于这个思路。

(引自http://blog.liancheng.info/?p=85

如果要实现多个指针对象共享内存,scope_ptr显然无能为力,而使用裸指针又要存在共享内存区何时释放的问题。要解决这个问题,还需要另外编写一个内存管理器,这意味着内存共享者之间的依赖关系会被加强,导致代码失去重用性,且增加了复杂性。而shared_ptr的出现就是为了解决上述问题。由于采用了非侵入式的方法,程序计数会交给shared_ptr自动执行,资源本身并不清楚被引用的次数。这样一方面可以解决前面我们碰到的部分问题,而且简化了计数等管理操作,但同时它还会引发新的问题。请看下面一段代码:

boost::asio::io_service io_service;

typedef boost::shared_ptr<client> Client;

vector<boost::shared_ptr<client>> vt;

while(i < 1000)

{

i++;

 

boost::shared_ptr<client> Client(new client(io_service, argv[1], argv[2]));

vt.push_back(Client);

}

boost::thread t(boost::bind(&boost::asio::io_service::run,&io_service));

t.join();

io_service.run();

return 0;

这里的vector vt本身没有任何逻辑上的用途,但是如果不加上这一条语句,由于client对象被一个shared_ptr所引用了,故在while执行完后,此内存空间就会因为引用对象数量为0而被解析掉。这样程序的执行就会出现异常错误。为了避免异常的出现,我们就不得不另外加上一个“多余的”变量,来共享对象。这样一来,我们的程序就违背了使用智能指针的初衷--我们已经无法完全的把内存释放这一棘手的问题完全交给智能指针解决了。

谈到这里不得不谈一下shared_ptr使用过程当中所存在的一个问题。对于某一个声明为shared_ptr的资源对象,由于其生命期依赖于其引用对象,这样只要有引用对象要使用这个资源,就一定要声明为shared_ptr类型的指针。如此,就会导致这种shared_ptr不断地向程序当中的其它去角落扩展,只要原始资源还存在利用价值,这种扩展就不会停止。

最后要说的就是如果要从this创建一个shared_ptr,也就是说在资源的内部创建它自己的shared_ptr,应该如何实现呢?在《beyond the C++ Standard library》一书当中,提到了boost中的两种实现机制。一种是通过引入一个weak_ptr观察指针来实现,这个我们以后再详细讨论。另外一种就是通过其提供的一个称为enable_shared_from_this的辅助类,只需要通过对于这个类显式的继承就可以实现上述的功能。但是,这样的继承体系就是对于资源对象的一种要求,无形中又将问题转换成了侵入式的问题。

对于boost库当中智能指针暂时先研究到这里,更加深入的内容稍后待续。总之一点,虽然智能指针可以带来很多内存管理上的便利,但是它看起来并不是一种“万能的”解决方案,内存管理当中的一切问题也不可能全部交给它来完成,更加复杂的问题还需要我们程序员开动脑筋去解决。

好,今天的C++科普总结先到这里,欢迎继续关注。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值