C++智能指针

c++智能指针:auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三个是c++11支持,并且第一个已经被c++11弃用。

为什么要使用智能指针:

智能指针是一个RAII(Resource Acquisition is initialization)类模型,用来动态的分配内存。它提供所有普通指针提供的接口,却很少发生异常。在构造中,它分配内存,当离开作用域时,它会自动释放已分配的内存。这样的话,程序员就从手动管理动态内存的繁杂任务中解放出来了。

我们知道c++的内存管理是让很多人头疼的事,当我们写一个new语句时,一般就会立即把delete语句直接也写了,但是我们不能避免程序还未执行到delete时就跳转了或者在函数中没有执行到最后的delete语句就返回了,如果我们不在每一个可能跳转或者返回的语句前释放资源,就会造成内存泄露。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。

 

C++98提供了第一种智能指针:auto_ptr
auto_ptr还是足够智能的,但是它还是有一些根本性的破绽的。当把一个auto_ptr赋给另外一个auto_ptr时,它的所有权(ownship)也转移了。当我在函数间传递auto_ptr时,这就是一个问题。

话说,我在Foo()中有一个auto_ptr,然后在Foo()中我把指针传递给了Fun()函数,当Fun()函数执行完毕时,指针的所有权不会再返还给Foo。还有另外一个缺点。auto_ptr不能指向一组对象,就是说它不能和操作符new[]一起使用。因为当auto_ptr离开作用域时,delete被默认用来释放关联的内存空间。当auto_ptr只指向一个对象时,这当然是没问题的,但是在上面的代码里,我们在堆里创建了一组对象,应该使用delete[]来释放,而不是delete.auto_ptr不能和标准容器(vector,list,map....)一起使用。因为auto_ptr容易产生错误,所以它也将被废弃了。

C++11提供了一组新的智能指针,每一个都各有用武之地。
shared_ptr
unique_ptr
weak_ptr

第一种智能指针是shared_ptr,它有一个叫做共享所有权(sharedownership)的概念。shared_ptr的目标非常简单:多个指针可以同时指向一个对象,当最后一个shared_ptr离开作用域时,内存才会自动释放。使用make_shared宏来加速创建的过程。因为shared_ptr主动分配内存并且保存引用计数(reference count),make_shared 以一种更有效率的方法来实现创建工作。shared_ptr<int> sptr1 = make_shared<int>(100);上面的代码创建了一个shared_ptr,指向一块内存,该内存包含一个整数100,
以及引用计数1.如果通过sptr1再创建一个shared_ptr,引用计数就会变成2. 该计数被称为强引用(strong reference),除此之外,shared_ptr还有另外一种引用计数叫做弱引用(weak reference)通过调用use_count()可以得到引用计数, 据此你能找shared_ptr的数量。当debug的时候,可以通过观察shared_ptr中strong_ref的值得到引用计数。shared_ptr默认调用delete释放关联的资源。如果用户采用一个不一样的析构策略时,他可以自由指定构造这个shared_ptr的策略。shared_ptr指向一组对象,但是当离开作用域时,默认的析构函数调用delete释放资源。实际上,我们应该调用delete[]来销毁这个数组。用户可以通过调用一个函数,例如一个lamda表达式,来指定一个通用的释放步骤。shared_ptr<Test> sptr1( new Test[5], [ ](Test* p) { delete[ ] p; } );
就像一个普通指针一样,shared_ptr也提供解引用操作符*,->。除此之外,它还提供了一些更重要的接口:get(): 获取shared_ptr绑定的资源.reset(): 释放关联内存块的所有权,如果是最后一个指向该资源的shared_ptr,就释放这块内存。unique: 判断是否是唯一指向当前内存的shared_ptr.operator bool : 判断当前的shared_ptr是否指向一个内存块,可以用if 表达式判断。

唯一的问题是会产生产生了循环引用.
为了解决循环引用,C++提供了另外一种智能指针:weak_ptr

weak_ptr 拥有共享语义(sharing semantics)和不包含语义(not owning semantics)。这意味着,weak_ptr可以共享shared_ptr持有的资源。所以可以从一个包含资源的shared_ptr创建weak_ptr。weak_ptr不支持普通指针包含的*,->操作。它并不包含资源所以也不允许程序员操作资源。既然如此,我们如何使用weak_ptr呢?
答案是从weak_ptr中创建shared_ptr然后再使用它。
通过增加强引用计数,当使用时可以确保资源不会被销毁。当引用计数增加时,可以肯定的是从weak_ptr中创建的shared_ptr引用计数至少为1.否则,
当你使用weak_ptr就可能发生如下问题:当shared_ptr离开作用域时,其拥有的资源会释放,从而导致了混乱。可以以shared_ptr作为参数构造weak_ptr.从shared_ptr创建一个weak_ptr增加了共享指针的弱引用计数(weak reference),意味着shared_ptr与其它的指针共享着它所拥有的资源。但是当shared_ptr离开作用域时,这个计数不作为是否释放资源的依据。
换句话说,就是除非强引用计数变为0,才会释放掉指针指向的资源,在这里,弱引用计数(weak reference)不起作用。
将一个weak_ptr赋给另一个weak_ptr会增加弱引用计数(weak reference count)。所以,当shared_ptr离开作用域时,其内的资源释放了,这时候指向该shared_ptr的weak_ptr发生了什么?weak_ptr过期了(expired)。
如何判断weak_ptr是否指向有效资源,有两种方法:
调用use-count()去获取引用计数,该方法只返回强引用计数,并不返回弱引用计数。
调用expired()方法。比调用use_count()方法速度更快。

从weak_ptr调用lock()可以得到shared_ptr或者直接将weak_ptr转型为shared_ptr

unique_ptr也是对auto_ptr的替换。unique_ptr遵循着独占语义。在任何时间点,资源只能唯一地被一个unique_ptr占有。当unique_ptr离开作用域,所包含的资源被释放。如果资源被其它资源重写了,之前拥有的资源将被释放。所以它保证了他所关联的资源总是能被释放。unique_ptr的创建方法和shared_ptr一样,除非创建一个指向数组类型的unique_ptr。unique_ptr提供了创建数组对象的特殊方法,当指针离开作用域时,调用delete[]代替delete。当创建unique_ptr时,这一组对象被视作模板参数的部分。这样,程序员就不需要再提供一个指定的析构方法,如下:unique_ptr<int[ ]> uptr( new int[5] );当把unique_ptr赋给另外一个对象时,资源的所有权就会被转移。记住unique_ptr不提供复制语义(拷贝赋值和拷贝构造都不可以),只支持移动语义(move semantics).unique_ptr提供的接口和传统指针差不多,但是不支持指针运算。unique_ptr提供一个release()的方法,释放所有权。release和reset的区别在于,release仅仅释放所有权但不释放资源,reset也释放资源。完全取决于你想要如何拥有一个资源,如果需要共享资源使用shared_ptr,如果独占使用资源就使用unique_ptr.除此之外,shared_ptr比unique_ptr更加重,因为他还需要分配空间做其它的事,比如存储强引用计数,弱引用计数。而unique_ptr不需要这些,它只需要独占着保存资源对象。

 

使用时的注意事项:

1、应该避免把auto_ptr放到容器中,因为算法对容器操作时,很难避免STL内部对容器实现了赋值传递操作,这样会使容器中很多元素被置为NULL。

2、unique_ptr,是用于取代c++98的auto_ptr的产物。

为什么摒弃auto_ptr?
先来看下面的赋值语句:

auto_ptr< string> ps (new string ("I reigned lonely as a cloud.”);
auto_ptr<string> vocation; 
vocaticn = ps;
上述赋值语句将完成什么工作呢?如果ps和vocation是常规指针,则两个指针将指向同一个string对象。这是不能接受的,因为程序将试图删除同一个对象两次——一次是ps过期时,另一次是vocation过期时。要避免这种问题,方法有多种:

定义陚值运算符,使之执行深复制。这样两个指针将指向不同的对象,其中的一个对象的副是另一个对象本,缺点是浪费空间,所以智能指针都未采用此方案。
建立所有权(ownership)概念。对于特定的对象,只能有一个智能指针可拥有,这样只有拥有对象的智能指针的构造函数会删除该对象。然后让赋值操作转让所有权。这就是用于auto_ptr和uniqiie_ptr 的策略,但unique_ptr的策略更严格。创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。例如,赋值时,计数将加1,而指针过期时,计数将减1,。当减为0时才调用delete。这是shared_ptr采用的策略。当然,同样的策略也适用于复制构造函数。每种方法都有其用途,但为何说要摒弃auto_ptr呢?下面举个例子来说明。
#include <iostream>
#include <string>
#include <memory>
using namespace std;

int main() {
  auto_ptr<string> films[5] =
 {
  auto_ptr<string> (new string("Fowl Balls")),
  auto_ptr<string> (new string("Duck Walks")),
  auto_ptr<string> (new string("Chicken Runs")),
  auto_ptr<string> (new string("Turkey Errors")),
  auto_ptr<string> (new string("Goose Eggs"))
 };
 auto_ptr<string> pwin;
 pwin = films[2]; // films[2] loses ownership. 将所有权从films[2]转让给pwin,此时films[2]不再引用该字符串从而变成空指针

 cout << "The nominees for best avian baseballl film are\n";
 for(int i = 0; i < 5; ++i)
  cout << *films[i] << endl;
 cout << "The winner is " << *pwin << endl;
 cin.get();

 return 0;
}

运行下发现程序崩溃了,原因在上面注释已经说的很清楚,films[2]已经是空指针了,下面输出访问空指针当然会崩溃了。但这里如果把auto_ptr换成shared_ptr或unique_ptr后,程序就不会崩溃,原因如下:

使用shared_ptr时运行正常,因为shared_ptr采用引用计数,pwin和films[2]都指向同一块内存,在释放空间时因为事先要判断引用计数值的大小因此不会出现多次删除一个对象的错误。
使用unique_ptr时编译出错,与auto_ptr一样,unique_ptr也采用所有权模型,但在使用unique_ptr时,程序不会等到运行阶段崩溃,而在编译器因下述代码行出现错误:
unique_ptr<string> pwin;
pwin = films[2]; // films[2] loses ownership.
指导你发现潜在的内存错误。
这就是为何要摒弃auto_ptr的原因,一句话总结就是:避免潜在的内存崩溃问题。

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值