C++之智能指针总结

来源:互联网 发布:在手机淘宝怎么删评价 编辑:程序博客网 时间:2024/05/24 02:38

C++提供了4种智能指针用于对分配的内存进行自动释放。
即auto_ptr、unique_ptr、share_ptr、weak_ptr。这4种智能指针都使用模板实现。
智能指针的行为类似于指针的类对象。它使用设计模式中的代理模式,即代理了原始“裸”指针的行为,为指针添加了更多更有用的特性。C++在引入异常机制后,智能指针有一种技巧升级为一种非常重要的技术,因为如果没有智能指针,程序员必须保证new对象能在正确的时机delete,四处编写异常捕获代码释放资源,而智能指针则可以在退出作用域时(不论是正常离开或是异常离开)总调用delete来析构在堆栈上动态分配的对象。当然正常退出对象作用域也会自动调用析构函数释放在堆栈上动态分配的对象。由此可知将“裸”指针包装成智能指针对象可以实现动态分配内存对象的自动释放。而且智能指针对象也可以像原始指针那样使用运算符。后面在share_ptr中会进行印证。
(1) auto_ptr
“自动指针”auto_ptr很好用,它部分解决了获取资源自动释放的问题。被包含在C++标准库中令它在世界范围内被广泛使用,使用智能指针的思想、用法深入人心。但标注库没有覆盖智能指针的全部领域,尤其最重要的引用计数型智能指针。auto_ptr的构造函数接受new操作符或者对象工厂创建出的对象指针作为参数,从而代理了原始指针。虽然它是一个对象,但因为重载了 operator*后operator->,其行为非常类似指针,可以把它用在大多数普通指针可用的地方。当退出作用域时(离开作用域或异 常),C++会保证auto_ptr对象销毁,调用auto_ptr的析构函数,进而使用delete操作符删除原始指针释放资源。

#include <iostream>#include <memory>#include <string>using namespace std;class Report{private:    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(){        auto_ptr<Report> ps (new Report("Using auto_ptr."));        ps->comment(string("ps:"));        auto_ptr<Report> p1;        p1 = ps;  //赋值完毕后ps已经失去对内存对象的所有权,不可再使用        p1->comment(string("p1:"));        //ps->comment(string("after p1=ps:"));  //错误        return 0;}

结果:
Object created!
ps: Using auto_ptr.
pl: Using auto_ptr.
Object deleted!
(2) shared_ptr
这是一个最像智能指针的“智能指针”,是源自boost库,后被收录到C++11标准的TR1库中。需要强调的是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是用于管理new动态分配对象的智能指针,它重载了*和->操作符以模仿原始指针的行为,提供隐式bool类型转换以判断指针的有效性,get()函数可以得到指针原始指针,并且没有提供指针算术操作。
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( )返回当前指针的引用计数。
要小心,use_count( )应该仅仅用于测试或者调试,它不提供高效率的操作,而且有的时候可能是不可用的(极少数情形)。而unique( )则是可靠的,任何时候都可用,而且比use_count( ) == 1 速度更快。
shared_ptr还支持比较运算符,可以测试两个shared_ptr的相等或者不等,比较基于内部保存的指针,相当于a.get( ) == b.get( )。
shared_ptr还可以使用operator<比较大小,同样基于内部保存的指针,但不提供除operator<以外的比较操作符,这使得shared_ptr可以被用于标准关联容器(set 和 map):
typedef shared_ptr sp_t; //shared_ptr类型定义
map

shared_ptr<int> sp (new int(10));assert( sp.unique( ));    //现在shared_ptr是指针的唯一持有者shared_ptr<int> 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<int> p;public:     shared (shared_ptr<int> p_):p(p_) { }     void print( ) { cout <<"count:"<<p.use_count() <<"v="<<*P << endl;}};void print_func(shared_ptr<int> p){    //同样输出shared_ptr的引用计数和指向的值    cout << "count:" << p.use_count() << "v =" << *P << endl;}int main( ){     shared_ptr<int > p( new int(100) );     shared s1(p), s2(p);     //     s1.print( );     s2.print( );     //     *p = 20;     print_func(p);     //     s1.print( );}

结果:
cout:3 v=100
cout:3 v=100
cout:4 v=20
cout:3 v=20
在声明了shared_ptr和两个shared_ptr类实例后,指针被它们所共享,因此引用计数为3。print_func( )函数内部拷贝了一个shared_ptr对象,因此引用计数再增加1,但当退出函数时拷贝自动析构,引用计数又恢复为3。
(3) unique_ptr
请看下面的语句:

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

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

unique_ptr<string> p3 (new string("auto"));  //4unique_ptr<string> p4;      //5p4 = 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_prt,然后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 allowedunique_ptr<string> pu3;pu3 = unique_ptr<string>(new string("Yo!"));   //允许

语句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<string> ps1,ps2;ps1 = demo("Uniquely special");ps2 = std::move(ps1);   //enable assignment,启用分配ps1 = demo(" and more");cout << *ps2 << *ps1 << endl;

可能会问,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

#include <iostream>#include <memory>int main () {  std::default_delete<int> d;  std::unique_ptr<int> u4 (new int, d);  std::unique_ptr<int> u5 (new int, std::default_delete<int>());  std::cout << "u4: " << (u4?"not null":"null") << '\n';  std::cout << "u5: " << (u5?"not null":"null") << '\n'  return 0;}
0 0
原创粉丝点击