C++智能指针使用总结

C++智能指针使用总结


C++提供了4种智能指针用于对分配的内存进行自动释放,这些智能指针如下:

      auto_ptr、unique_ptr、shared_ptr、weak_ptr。其中auto_ptr在C++98标准引入,后三种在C++11标准中加入。而auto_ptr已经被C++11所摒弃,建议使用后三种智能指针,这4种智能指针使用模板(template)实现。在此总结下个人对这4种智能指针肤浅认识。

C++智能指针简介

  1. 1

           C++智能指针是行为类似于指针的类对象。它使用设计模式中的代理模式,代理了原始“裸”指针的行为,为指针添加了更多更有用的特性。

           C++引入异常机制后,智能指针由一种技巧升级为一种非常重要的技术,因为如果没有智能指针,程序员必须保证new对象能在正确的时机delete,四处编写异常捕获代码释放资源,而智能指针则可以在退出作用域时——不管是正常离开或是因异常离开——总调用delete来析构在堆栈上动态分配的对象。

           因为C++异常处理的真正功能在于它具有为异常抛掷前构造的所有局部对象(那么智能指针对象也适用)自动调用析构函数的能力(C++异常机制不仅仅在于它能够处理各种不同类型的异常)。所以在异常退出智能指针对象作用域时,总能由C++异常机制调用析构函数释放在堆栈上动态分配的对象。

           当然,正常退出对象(智能指针对象也属于此列)作用域也会自动调用析构函数释放在堆栈上动态分配的对象。

           由此可知,将“裸”指针包装成智能指针对象可以实现动态分配的内存对象的自动释放。

           而且C++智能指针对象可以像原指针那样直接使用运算符,如赋值运算符'=',指针运算符'->',解引用运算符'*'。这点可以从下面的”shared_ptr智能指针--shared_ptr模板类摘要“部分可以印证。

    END

auto_ptr智能指针(C++98)

  1. 1

    所属头文件:#include <memory>

    所属命名空间及标识符:using std::shared_ptr;

    所属版本:C++98

    g++启用版本命令:g++ -std=c++98 -c -o

    补充:

           如果启用c++11及以上标准,即g++ -std=c++11 -c -o,编译时会有一个警告信息提示

    warning:‘auto_ptr’ is deprecated  (‘auto_ptr‘被反对使用)

  2. 2

           存在很多种智能指针,其中最有名的应该是C++98标准中的“自动指针”std::auto_ptr,它部分解决了获取资源自动释放的问题,例如:

    #include <memory>

    #include <iostream>

    #include <string>

    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<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:"));  //error,Segmentation faul

    }

    /*Result:

    Object created!

    Using auto_ptr.

    Object deleted!

    */

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

  3. 3

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

    END

shared_ptr智能指针(C++11)

  1. 1

    所属头文件:#include <memory>

    所属命名空间及标识符:using std::shared_ptr;

    所属版本:C++11

    g++启用版本命令:g++ -std=c++11 -c -o

  2. 2

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

           在C++历史上曾经出现过无数的引用计数型智能指针实现,但没有一个比得上shared_ptr,在过去、现在和将来,它都是最好的。

  3. 3

    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);

    }

  4. 4

    操作函数:

           shared_ptr是用于管理new动态分配对象的智能指针,它重载了*和->操作符以模仿原始指针的行为,提供隐式bool类型转换以判断指针的有效性,get()函数可以得到指针原始指针,并且没有提供指针算术操作。例如:

    #include <memory>

    #include <assert>

    //

    using std::shared_ptr;

    using std::assert;

    //

    shared_ptr<int> spi (new int);  //一个int的shared_ptr

    assert(spi);   //在bool语境中隐式转换为bool值

    *spi = 253;   //使用解引用操作符*

    shared_ptr<string> sps(new string("smart"));   //一个string的shared_ptr

    assert(sps->size( ) == 5);   //使用->运算符

  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<Y> & r)从一个auto_ptr获得指针的管理权,引用

        计数置为1,同时auto_ptr自动失去管理权;

    ▲ operator=赋值操作符可以从另外一个shared_ptr或auto_ptr获得指针的

        管理权,其行为同构造函数;

    ▲ shared_ptr( Y *p, D d)行为类似shared_ptr(Y * p),但使用参数d指定了

         析构时的定制删除器,而不是简单的delete。

  6. 6

           shared_ptr的reset( )函数的作用是将引用计数减1,停止对指针的共享,除非引用计数为0,否则不会发生删除操作。

           shared_ptr有两个专门的函数检查引用计数。unique( )在shared_ptr是指针的唯一拥所有者时返回true。use_count( )返回当前指针的引用计数。

           要小心,use_count( )应该仅仅用于测试或者调试,它不提供高效率的操作,而且有的时候可能是不可用的(极少数情形)。而unique( )则是可靠的,任何时候都可用,而且比use_count( ) == 1 速度更快。

  7. 7

           shared_ptr还支持比较运算符,可以测试两个shared_ptr的相等或者不等,比较基于内部保存的指针,相当于a.get( ) == b.get( )。

           shared_ptr还可以使用operator<比较大小,同样基于内部保存的指针,但不提供除operator<以外的比较操作符,这使得shared_ptr可以被用于标准关联容器(set 和 map):

    typedef shared_ptr<string> sp_t;   //shared_ptr类型定义

    map<sp_t, int> m;   //标准映射容器

    sp_t sp(new string("one"));   //一个shared_ptr对象     

    m[sp] = 111;   //关联数组用法

  8. 8

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

  9. 9

    用法举例

    A.示范shared_ptr基本用法的例子如下:

    shared_ptr<int> sp (new int(10));

    assert( sp.unique( ));    //现在shared_ptr是指针的唯一持有者

    shared_ptr<int> sp2;  //生成一个空(NULL)的智能指针

    sp2 = sp;   //sp2和sp指向同一个对象,拷贝构造函数

    //两个shared_ptr相等,指向同一个对象,引用计数为2

    assert( 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。

    END

unique_ptr智能指针(C++11)

  1. 1

    所属头文件:#include <memory>

    所属命名空间及标识符:using std::shared_ptr;

    所属版本:C++11

    g++启用版本命令:g++ -std=c++11 -c -o

  2. 2

    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更安全(编译阶段错误比潜在的程序崩溃更安全)。

  3. 3

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

    #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++编译器支持这种特性)确实允许这种赋值!

  4. 4

           总之,程序试图将一个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的操作可能导致不确定的行为和神秘崩溃。

           当然,您可能确实想执行类似于语句#1的操作。仅当以非智能的方式使用遗弃的智能指针(如解除引用时),这种赋值才不安全。要安全地重用这种指针,可以给它赋新值。C++有一个标准库函数std::move( ),让您能够将一个unique_ptr赋给另一个。下面是一个使用前述demo( )函数的例子,该函数返回一个unique_ptr<string>对象:

    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;

  5. 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<double[ ]> pda (new double[5]);  //将使用delete[ ]

  6. 6

    std::default_delete

                   ——  配合unique_ptr使用

    ----------------------------------------------------------------------------

    non-specialized          template <class T> class default_delete;

    array specialization     template <class T> class default_delete<T[]>;

    模板参数: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 <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;

    }

    END

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

  1. 1

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

    ▲不使用new分配内存时,不能使用auto_ptr或shared_ptr;

    ▲不使用new或new[ ]分配内存时,不能使用unique_ptr;

    END

选择智能指针


从零开始学编程 http://www.c0ks.com
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值