C++ 智能指针

介绍

C++提供了4种智能指针用于对分配的内存进行自动释放,这些智能指针如下:auto_ptr、unique_ptr、shared_ptr、weak_ptr。其中auto_ptr在C++98标准引入,后三种在C++11标准中加入。而auto_ptr已经被C++11所摒弃,建议使用后三种智能指针,这4种智能指针使用模板(template)实现。

C++11将boost里的这一套纳入了标准。

1.auto_ptr

auto_ptr的构造函数接受new操作符或者对象工厂创建出的对象指针作为参数,从而代理了原始指针。虽然它是一个对象,但因为重载了 operator*后operator->,其行为非常类似指针,可以把它用在大多数普通指针可用的地方。当退出作用域时(离开作用域或异常),C++会保证auto_ptr对象销毁,调用auto_ptr的析构函数,进而使用delete操作符删除原始指针释放资源。

auto_ptr很好用,被包含在C++标准库中令它在世界范围内被广泛使用,使用智能指针的思想、用法深入人心。但标注库没有覆盖智能指针的全部领域,尤其最重要的引用计数型智能指针。

2.unique_ptr

官方文档 地址

unique_ptr为何优于auto_ptr

请看下面的语句:

auto_ptr<string> p1 (new string("auto"));  //#1
auto_ptr<string> p2;                       //#2
p2 = p1;                                   //#3                                          

在语句#3中,p2接管string对象的所有权后,p1的所有权将被剥夺。这是件好事,可以防止p1和p2的析构函数试图删除同一个对象;但如果程序随后试图使用p1,这将是件坏事,因为p1不再指向有效的数据。

下面来看看使用unique_ptr的情况:

unique_ptr<string> p3 (new string("auto"));  //#4
unique_ptr<string> p4;                       //#5            
p4 = p3;                                     //#6                      

编译器认为语句#6非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全(编译阶段错误比潜在的程序崩溃更安全)。

但有时候,将一个智能指针赋值给另一个并不会留下危险的悬挂指针(就是空指针,极有可能被误用)。假设有如下函数定义:

#include <iostream>
#include <memory>
#include <string>
using std::string;
using std::cout;
using std::unique_ptr;
class Report
{
private:
    string str;
public:
    Report( const string s):str(s) { cout << "Object created!\n"; }
    ~Report() { cout << "Object deleted!\n"; }
    void comment(const string owner) const {
           cout << owner << str << "\n";
     }
};
unique_ptr<Report> demo(const char *s)
{
    unique_ptr<Report> temp(new Report(s));
    return temp;
}
int main(void)
{
    unique_ptr<Report> ps;
    ps = demo("Uniquely special point");
    ps->comment(string("un_ptr:"));
    return 0;
}

demo( )返回一个临时的unique_ptr,然后ps接管了原本归返回的unique_ptr所有的对象,而返回的unique_ptr被销毁。这没有问题,因为ps拥有了Report对象的所有权。这里还有另一个好处是,demo()返回的临时unique_ptr很快被销毁(因为由函数调用而返回的临时对象在堆中使用完后会被销毁),没有机会使用它来访问无效的数据。换句话说,没有理由禁止这种赋值。神奇的是,编译器(GUN GCC g++编译器支持这种特性)确实允许这种赋值!

总之,程序试图将一个unique_ptr赋给另一个时,如果源unique_ptr是个临时右值,编译器允许这样做;如果源unique_ptr将存在一段时间,编译器将禁止这样做:

using std::unique_ptr;
using std::string;
unique_ptr<string> pu1(new string("Hi ho!"));
unique_ptr<string> pu2;
pu2 = pu1;                                      //#not allowed
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string("Yo!"));   //#allowed

语句#1将留下悬挂的unique_ptr(pu1),这句可能导致危害。语句#2不会留下悬挂的unique_ptr,因为它调用unique_ptr的构造函数,该构造函数创建的临时对象在其所有权转让给pu后就被销毁。这种情况而异的行为表明,unique_ptr优于允许两种赋值的auto_ptr。

这也是禁止(只是一种建议,编译器并不禁止)在容器对象中使用auto_ptr,但允许使用unique_ptr的原因。如果容器算法视图对包含unique_ptr的容器执行类似于语句#1的操作,将导致编译错误;如果算法视图执行类似于语句#2的操作,则不会有任何问题。而对auto_ptr,类似于语句#1的操作可能导致不确定的行为和神秘崩溃。

相比于auto_ptr,unique_ptr还有另一个优点,它有一个[]用于数组的变体。别忘了,必须将delete和new别对,将delete[]和new[]配对。模板auto_ptr使用delete而不是delete[],因此只能与new一起使用,而不能与new[]一起使用。但unique_ptr有使用new[]和delete[]的版本:

std::unique_ptr<double[ ]> pda (new double[5]);  //将使用delete[ ]

3.shared_ptr

shared_ptr模板类摘要:

template<class T>
class shared_ptr
{
public:
       typedef T element_type;

       shared_ptr();
       template<class Y> explicit shared_ptr(Y *p);
       template<class Y, class D> shared_ptr(Y *p, D d);
       ~shared_ptr();

       shared_ptr( shared_ptr const & r);
       template<calss Y> explicit  shared_ptr(std::auto_ptr<Y> & r);        

       shared_ptr &operator=(shared_ptr const & r);
       template<class Y> shared_ptr &operator=(shared_ptr<Y> const &r);
       template<class Y> shared_ptr &operator=(std::auto_ptr<Y> & r);

       void reset( );
       template<class Y> void reset(Y * p);
       template<class Y, class D> void reset( Y * p, D d);

       T & operator*( )const;
       T * operator->( ) const;
       T * get( ) const;

       bool unqiue( ) const;
       long use_count( ) const;

       operator unspecified-bool-type( ) const;
       void swap(shared_ptr & b);
}

shared_ptr可以被安全的共享——shared_ptr是一个“全功能”的类,有着正常的拷贝、赋值语义,也可以进行shared_ptr间的比较,是“最智能”的智能指针。

shared_ptr函数介绍:

  • 无参的shared_ptr( )创建一个持有空指针的shared_ptr;
  • shared_ptr(Y *p)获得指向类型T的指针p的管理权,同时引用计数置为1。这个构造函数要求Y类型必须能够转换为T类型;
  • shared_ptr(shared_ptr const & r)从另外一个shared_ptr获得指针的管理权,同时引用计数加1,结果是两个shared_ptr共享一个指针的管理权;
  • shared_ptr(std::auto_ptr & r)从一个auto_ptr获得指针的管理权,引用计数置为1,同时auto_ptr自动失去管理权;
  • operator=赋值操作符可以从另外一个shared_ptr或auto_ptr获得指针的管理权,其行为同构造函数;
  • shared_ptr( Y p, D d)行为类似shared_ptr(Y p),但使用参数d指定了析构时的定制删除器,而不是简单的delete。
  • shared_ptr的reset( )函数的作用是将引用计数减1,停止对指针的共享,除非引用计数为0,否则不会发生删除操作。
  • shared_ptr有两个专门的函数检查引用计数。unique( )在shared_ptr是指针的唯一拥所有者时返回true。use_count( )返回当前指针的引用计数。

shared_ptr中所实现的本质是引用计数(reference counting),也就是说shared_ptr是支持复制的,复制一个shared_ptr的本质是对这个智能指针的引用次数加1,而当这个智能指针的引用次数降低到0的时候,该对象自动被析构

需要特别指出的是,如果shared_ptr所表征的引用关系中出现一个环,那么环上所述对象的引用次数都肯定不可能减为0那么也就不会被删除,为了解决这个问题引入了weak_ptr。

4.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,实质上这个环就可以被打破了,原有的环状引用带来的无法析构的问题也就随之得到了解决。

5.智能指针的选择

(1)如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。这样的情况包括:

  • 有一个指针数组,并使用一些辅助指针来标示特定的元素,如最大的元素和最小的元素;
  • 两个对象包含都指向第三个对象的指针;
  • STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出warning)和auto_ptr(行为不确定)。如果你的编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr。

(2)如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。如果函数使用new分配内存,并返还指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。可将unique_ptr存储到STL容器在那个,只要不调用将一个unique_ptr复制或赋给另一个算法(如sort())

参考文献
(1)http://www.cnblogs.com/lanxuezaipiao/p/4132096.html
(2)http://www.zhihu.com/question/20368881

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值