多语言展示
当前在线:1237今日阅读:84今日分享:32

C++智能指针使用总结

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

GNU GCC g++ 4.8.1 C++编译器

C++智能指针简介

C++智能指针是行为类似于指针的类对象。它使用设计模式中的代理模式,代理了原始“裸”指针的行为,为指针添加了更多更有用的特性。       C++引入异常机制后,智能指针由一种技巧升级为一种非常重要的技术,因为如果没有智能指针,程序员必须保证new对象能在正确的时机delete,四处编写异常捕获代码释放资源,而智能指针则可以在退出作用域时——不管是正常离开或是因异常离开——总调用delete来析构在堆栈上动态分配的对象。       因为C++异常处理的真正功能在于它具有为异常抛掷前构造的所有局部对象(那么智能指针对象也适用)自动调用析构函数的能力(C++异常机制不仅仅在于它能够处理各种不同类型的异常)。所以在异常退出智能指针对象作用域时,总能由C++异常机制调用析构函数释放在堆栈上动态分配的对象。       当然,正常退出对象(智能指针对象也属于此列)作用域也会自动调用析构函数释放在堆栈上动态分配的对象。       由此可知,将“裸”指针包装成智能指针对象可以实现动态分配的内存对象的自动释放。       而且C++智能指针对象可以像原指针那样直接使用运算符,如赋值运算符'=',指针运算符'->',解引用运算符'*'。这点可以从下面的”shared_ptr智能指针--shared_ptr模板类摘要“部分可以印证。

auto_ptr智能指针(C++98)
1

所属头文件:#include 所属命名空间及标识符:using std::shared_ptr;所属版本:C++98g++启用版本命令:g++ -std=c++98 -c -o补充:       如果启用c++11及以上标准,即g++ -std=c++11 -c -o,编译时会有一个警告信息提示warning:‘auto_ptr’ is deprecated  (‘auto_ptr‘被反对使用)

2

存在很多种智能指针,其中最有名的应该是C++98标准中的“自动指针”std::auto_ptr,它部分解决了获取资源自动释放的问题,例如:#include #include #include using std::cin;using std::cout;using std::string;using std::auto_ptr;class Report{private:    std::string str;public:    Report( const string s ):str(s) { cout << 'Object created!\n'; }    ~Report() { cout << 'Object deleted!\n'; }    void comment(string owner) const { cout << owner << str << '\n'; }};int main(void){        auto_ptr ps (new Report('Using auto_ptr.'));        ps->comment(string('ps:'));        auto_ptr p1;        p1 = ps;  //赋值完毕后ps已经失去对内存对象的所有权,不可再使用        p1->comment(string('p1:'));        //ps->comment(string('after p1=ps:'));  //error,Segmentation faul}/*Result:Object created!Using auto_ptr.Object deleted!*/     auto_ptr的构造函数接受new操作符或者对象工厂创建出的对象指针作为参数,从而代理了原始指针。虽然它是一个对象,但因为重载了 operator*后operator->,其行为非常类似指针,可以把它用在大多数普通指针可用的地方。当退出作用域时(离开作用域或异 常),C++会保证auto_ptr对象销毁,调用auto_ptr的析构函数,进而使用delete操作符删除原始指针释放资源。

3

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

shared_ptr智能指针(C++11)
1

所属头文件:#include 所属命名空间及标识符:using std::shared_ptr;所属版本:C++11g++启用版本命令:g++ -std=c++11 -c -o

2

shared_ptr是一个最像智能指针的“智能指针”,是源自boost库,后被收录到C++11标准的TR1库中。“抱歉,我实在想不出什么更恰当的词汇来形容它在软件开发中的重要性。再强调一遍,shared_ptr非常有价值、非常重要、非常有用。”(摘自《Boost程序库完全开发指南:深入C++“准”标准库》   罗剑锋 著 P69)。       在C++历史上曾经出现过无数的引用计数型智能指针实现,但没有一个比得上shared_ptr,在过去、现在和将来,它都是最好的。

3

shared_ptr模板类摘要:templateclass shared_ptr{public:       typedef T element_type;       //       shared_ptr();       template explicit shared_ptr(Y *p);       template shared_ptr(Y *p, D d);       ~shared_ptr();       //       shared_ptr( shared_ptr const & r);       template explicit shared_ptr(std::auto_ptr & r);       //       shared_ptr &operator=(shared_ptr const & r);       template shared_ptr &operator=(shared_ptr const &r);       template shared_ptr &operator=(std::auto_ptr & r);       //       void reset( );       template void reset(Y * p);       template 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);}

4

操作函数:       shared_ptr是用于管理new动态分配对象的智能指针,它重载了*和->操作符以模仿原始指针的行为,提供隐式bool类型转换以判断指针的有效性,get()函数可以得到指针原始指针,并且没有提供指针算术操作。例如:#include #include //using std::shared_ptr;using std::assert;//shared_ptr spi (new int);  //一个int的shared_ptrassert(spi);   //在bool语境中隐式转换为bool值*spi = 253;   //使用解引用操作符*shared_ptr sps(new string('smart'));   //一个string的shared_ptrassert(sps->size( ) == 5);   //使用->运算符

5

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。

6

shared_ptr的reset( )函数的作用是将引用计数减1,停止对指针的共享,除非引用计数为0,否则不会发生删除操作。       shared_ptr有两个专门的函数检查引用计数。unique( )在shared_ptr是指针的唯一拥所有者时返回true。use_count( )返回当前指针的引用计数。       要小心,use_count( )应该仅仅用于测试或者调试,它不提供高效率的操作,而且有的时候可能是不可用的(极少数情形)。而unique( )则是可靠的,任何时候都可用,而且比use_count( ) == 1 速度更快。

7

shared_ptr还支持比较运算符,可以测试两个shared_ptr的相等或者不等,比较基于内部保存的指针,相当于a.get( ) == b.get( )。       shared_ptr还可以使用operator<比较大小,同样基于内部保存的指针,但不提供除operator<以外的比较操作符,这使得shared_ptr可以被用于标准关联容器(set 和 map):typedef shared_ptr sp_t;   //shared_ptr类型定义map m;   //标准映射容器sp_t sp(new string('one'));   //一个shared_ptr对象     m[sp] = 111;   //关联数组用法

8

shared_ptr还支持流输出操作符operator<<,输出内部的指针值,方便调试。

9

用法举例A.示范shared_ptr基本用法的例子如下:shared_ptr sp (new int(10));assert( sp.unique( ));    //现在shared_ptr是指针的唯一持有者shared_ptr sp2;  //生成一个空(NULL)的智能指针sp2 = sp;   //sp2和sp指向同一个对象,拷贝构造函数//两个shared_ptr相等,指向同一个对象,引用计数为2assert( sp == sp2 && sp.use_count( ) == 2 );*sp2 = 100;assert( *sp == 100 );sp.reset( );assert( !sp );////B.第二个示范shared_ptr复杂的应用:class shared{private:     shared_ptr p;public:     shared (shared_ptr p_):p(p_) { }     void print( ) { cout <<'count:'< p2;                                  //#2p2 = p1;                                                      //#3       在语句#3中,p2接管string对象的所有权后,p1的所有权将被剥夺。这是件好事,可以防止p1和p2的析构函数试图删除同一个对象;但如果程序随后试图使用p1,这将是件坏事,因为p1不再指向有效的数据。       下面来看看使用unique_ptr的情况:unique_ptr p3 (new string('auto'));  //#4unique_ptr p4;                                   //#5p4 = p3;                                                           //#6       编译器认为语句#6非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全(编译阶段错误比潜在的程序崩溃更安全)。

3

但有时候,将一个智能指针赋值给另一个并不会留下危险的悬挂指针(就是空指针,极有可能被误用)。假设有如下函数定义:#include #include #include 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 demo(const char *s){    unique_ptr temp(new Report(s));    return temp;}int main(void){    unique_ptr ps;    ps = demo('Uniquely special point');    ps->comment(string('un_ptr:'));    return 0;}//////////////////////////////////////////////////////////       demo( )返回一个临时的unique_prt,然后ps接管了原本归返回的unique_ptr所有的对象,而返回的unique_ptr被销毁。这没有问题,因为ps拥有了Report对象的所有权。这里还有另一个好处是,demo()返回的临时unique_ptr很快被销毁(因为由函数调用而返回的临时对象在堆中使用完后会被销毁),没有机会使用它来访问无效的数据。换句话说,没有理由禁止这种赋值。神奇的是,编译器(GUN GCC g++编译器支持这种特性)确实允许这种赋值!

4

总之,程序试图将一个unique_ptr赋给另一个时,如果源unique_ptr是个临时右值,编译器允许这样做;如果源unique_ptr将存在一段时间,编译器将禁止这样做:using std::unique_ptr;using std::string;unique_ptr pu1(new string('Hi ho!'));unique_ptr pu2;pu2 = pu1;    //#not allowedunique_ptr pu3;pu3 = unique_ptr(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的操作可能导致不确定的行为和神秘崩溃。       当然,您可能确实想执行类似于语句#1的操作。仅当以非智能的方式使用遗弃的智能指针(如解除引用时),这种赋值才不安全。要安全地重用这种指针,可以给它赋新值。C++有一个标准库函数std::move( ),让您能够将一个unique_ptr赋给另一个。下面是一个使用前述demo( )函数的例子,该函数返回一个unique_ptr对象:using std::unique_ptr;unique_ptr ps1,ps2;ps1 = demo('Uniquely special');ps2 = std::move(ps1);   //enable assignment,启用分配ps1 = demo(' and more');cout << *ps2 << *ps1 << endl;

5

您可能会问,unique_ptr如何能够区分安全和不安全的用法呢?答案是它使用了C++11新增的移动构造函数和右值引用,这将在18章(C++ primer 6th)讨论。       相比于auto_ptr,unique_ptr还有另一个优点,它有一个け用于数组的变体。别忘了,必须将delete和new别对,将delete[]和new[]配对。模板auto_ptr使用delete而不是delete[],因此只能与new一起使用,而不能与new[]一起使用。但unique_ptr有使用new[]和delete[]的版本:std::unique_ptr pda (new double[5]);  //将使用delete[ ]

6

std::default_delete               ——  配合unique_ptr使用----------------------------------------------------------------------------non-specialized          template class default_delete;array specialization     template class default_delete;模板参数:T类型T为待删除对象的类型。------------------------------默认的删除器(Default deleter)       函数对象类,其功能像调用(invokation)并删除一个类型为T*对象。       无参数的版本只是简单的使用delete执行内存释放操作。       同样(likewise), 以运行时确定数组长度的数组参数版本使用delete[]执行内存释放操作       此类用于保存unique_ptr实例中类型删除器而特意设计,或者用于定义一个删除器对象传递给unique_ptr 和 shared_ptr的构造函数。由于这是一个轻量级(lightweight)的无状态类,使用它作为unique_ptr对象的删除器,相对于C++内建指针(绝大多数库都实现了)它既不增加额外开销也不增加空间。--------------------------------  Example  ----------------------------------// unique_ptr constructor example#include #include int main () {  std::default_delete d;  std::unique_ptr u4 (new int, d);  std::unique_ptr u5 (new int, std::default_delete());  std::cout << 'u4: ' << (u4?'not null':'null') << '\n';  std::cout << 'u5: ' << (u5?'not null':'null') << '\n'  return 0;}

new和new[]与智能指针的正确搭配

▲使用new分配内存时,才能使用auto_ptr和shared_ptr,使用new[ ]分配内存时,不能使用它们;▲不使用new分配内存时,不能使用auto_ptr或shared_ptr;▲不使用new或new[ ]分配内存时,不能使用unique_ptr;

选择智能指针

P67316.2.4

推荐信息