zoukankan      html  css  js  c++  java
  • C++智能指针--shared_ptr

     shared_ptr是一个引用计数智能指针。用于共享对象的全部权。它能够从一个裸指针、还有一个shared_ptr、一个auto_ptr、或者一个weak_ptr构造。

    还能够传递第二个參数给shared_ptr的构造函数,它被称为删除器(deleter)。删除器用于处理共享资源的释放。这对于管理那些不是用new分配也不是用delete释放的资源时很实用。shared_ptr被创建后。就能够像普通指针一样使用了,除了一点。它不能被显式地删除。

    shared_ptr的比較重要的接口例如以下:

        template <class T> 
        explicit shared_ptr(T* p);
        这个构造函数获得给定指针p的全部权。參数p必须是指向T的有效指针。构造后引用计数设为1。

    唯一从这个构造函数抛出的异常是std::bad_alloc(仅在一种非常罕见的情况下发生,即不能获得引用计数器所需的空间)。

     
        template <class T,class D> 
        shared_ptr(T* p,D d);
        这个构造函数带有两个參数。第一个是shared_ptr将要获得全部权的那个资源。第二个是shared_ptr被销毁时负责释放资源的一个对象,被保存的资源将以d(p)的形式传给那个对象。假设引用计数器不能分配成功,shared_ptr抛出一个类型为std::bad_alloc的异常。
     
        shared_ptr(const shared_ptr& r);
        r中保存的资源被新构造的shared_ptr所共享,引用计数加一。这个构造函数不会抛出异常。
     
        template <class T> 
        explicit shared_ptr(const weak_ptr<T>& r);
        从一个weak_ptr构造shared_ptr。这使得weak_ptr的使用具有线程安全性,由于指向weak_ptr參数的共享资源的引用计数将会自增(weak_ptr不影响共享资源的引用计数)。

    假设weak_ptr为空(r.use_count()==0), shared_ptr抛出一个类型为bad_weak_ptr的异常。

     
        template <typename T> 
        shared_ptr(auto_ptr<T>& r);
        这个构造函数从一个auto_ptr获取r中保存的指针的全部权,方法是保存指针的一份拷贝并对auto_ptr调用release。

    构造后的引用计数为1,而r则变为空的。

    假设引用计数器不能分配成功,则抛出std::bad_alloc。

     
        ~shared_ptr();
        shared_ptr析构函数,对引用计数减一。假设计数为零,则保存的指针被删除。

    删除指针的方法是调用operator delete。或者,假设给定了一个运行删除操作的删除器对象,就把保存的指针作为唯一參数调用这个对象。析构函数不会抛出异常。

     
        shared_ptr& operator=(const shared_ptr& r);
        赋值操作共享r中的资源。并停止对原有资源的共享。赋值操作不会抛出异常。

     
        void reset();
        reset函数用于停止对保存指针的全部权的共享。共享资源的引用计数减一。
     
        T& operator*() const;
        这个操作符返回对已存指针所指向的对象的一个引用。假设指针为空,调用operator*会导致没有定义行为。这个操作符不会抛出异常。

     
        T* operator->() const;
        这个操作符返回保存的指针。这个操作符与operator*一起使得智能指针看起来象普通指针。

    这个操作符不会抛出异常。

     
        T* get() const;
        get函数是当保存的指针有可能为空时(这时 operator* 和 operator-> 都会导致没有定义行为)获取它的最好办法。注意,你也能够使用隐式布尔类型转换来測试shared_ptr是否包括有效指针。这个函数不会抛出异常。

     
        bool unique() const;
        这个函数在shared_ptr是它所保存指针的唯一拥有者时返回true;否则返回false。 unique不会抛出异常。

     
        long use_count() const;
        use_count 函数返回指针的引用计数。它在调试的时候特别实用,由于它能够在程序运行的关键点获得引用计数的快照。小心地使用它。由于在某些可能的shared_ptr实现中,计算引用计数可能是昂贵的,甚至是不行的。这个函数不会抛出异常。

     
        operator unspecified-bool-type() const;
        这是个到unspecified-bool-type类型的隐式转换函数,它能够在Boolean上下文中測试一个智能指针。假设shared_ptr保存着一个有效的指针,返回值为True。否则为false。注意,转换函数返回的类型是不确定的。把返回类型当成bool用会导致一些荒谬的操作,所以典型的实现採用了safe bool idiom,它非常好地确保了仅仅有可适用的Boolean測试能够使用。这个函数不会抛出异常。

     
        void swap(shared_ptr<T>& b);
        这能够非常方便地交换两个shared_ptr。

    swap函数交换保存的指针(以及它们的引用计数)。这个函数不会抛出异常。

     
        template <typename T,typename U>  shared_ptr<T> static_pointer_cast(const shared_ptr<U>& r);
        要对保存在shared_ptr里的指针运行static_cast。我们能够取出指针然后强制转换它,但我们不能把它存到还有一个shared_ptr里;新的shared_ptr会觉得它是第一个管理这些资源的。解决办法是用static_pointer_cast,使用这个函数能够确保被指对象的引用计数保持正确。

    static_pointer_cast不会抛出异常。

        使用shared_ptr的演示样例代码例如以下:
    {
    shared_ptr<int> pInt1;
    assert(pInt1.use_count() == 0);         // 还没有引用指针
    {
          shared_ptr<int> pInt2(new int(5));
          assert(pInt2.use_count() == 1);        // new int(5)这个指针被引用1次
    
        pInt1 = pInt2;
          assert(pInt2.use_count() == 2);       // new int(5)这个指针被引用2次
        assert(pInt1.use_count() == 2);
    }                                                   //pInt2离开作用域, 所以new int(5)被引用次数-1
    
    assert(pInt1.use_count() == 1);
    }         // pInt1离开作用域,引用次数-1,如今new int(5)被引用0次,所以销毁它

        假设资源的创建销毁不是以new和delete的方式进行的。该怎么办呢?通过前面的接口能够看到,shared_ptr的构造函数中能够指定删除器。

    演示样例代码例如以下:

    class FileCloser
    {
    public:
        void operator()(FILE *pf)
        {
             if (pf != NULL)
             {
                   fclose(pf);
                   pf = NULL;
             }
        }
    };
    
    shared_ptr<FILE> fp(fopen(pszConfigFile, "r"), FileCloser());

        在使用shared_ptr时,须要避免同一个对象指针被两次当成shard_ptr构造函数里的參数的情况。考虑例如以下代码:

    {
         int *pInt = new int(5);
         shared_ptr<int> temp1(pInt);
         assert(temp1.use_count() == 1);
         shared_ptr<int> temp2(pInt);
         assert(temp2.use_count() == 1);
    }      // temp1和temp2都离开作用域,它们都销毁pInt,会导致两次释放同一块内存

        正确的做法是将原始指针赋给智能指针后,以后的操作都要针对智能指针了。

    參考代码例如以下:

    1 {
    2      shared_ptr<int> temp1(new int(5));
    3      assert(temp1.use_count() == 1);
    4      shared_ptr<int> temp2(temp1);
    5      assert(temp2.use_count() == 2);
    6 }      // temp1和temp2都离开作用域,引用次数变为0。指针被销毁。
        另外,使用shared_ptr来包装this时,也会产生与上面类似的问题。

    考虑例如以下代码:

    class A
    {
    public:
            shared_ptr<A> Get()
            {
                 return shared_ptr<A>(this);
            }
    }
    
    shared_ptr<A> pA(new A());
    shared_ptr<A> pB = pA->Get();

        当pA和pB离开作用域时。会将堆上的对象释放两次。

    怎样解决上述问题呢?C++ 11提供了例如以下机制:将类从enable_shared_from_this类派生,获取shared_ptr时使用shared_from_this接口。參考代码例如以下:

    class A :public enable_shared_from_this<A>
    {
    public:
            shared_ptr<A> Get()
            {
                 return shared_from_this();
            }
    }

        在多线程中使用shared_ptr时,假设存在拷贝或赋值操作,可能会因为同一时候訪问引用计数而导致计数无效。

    解决方法是向每一个线程中传递公共的week_ptr,线程中须要使用shared_ptr时。将week_ptr转换成shared_ptr就可以。

    你能够用下列方法把 shared_ptr 传递给还有一个函数:

    • shared_ptr 传递值。 调用复制构造函数。递增引用计数。并把被调用方当做全部者。还有就是在这次操作中有少量的开销,这非常大程度上取决于你传递了多少 shared_ptr 对象。

      当调用方和被调用方之间的代码协定 (隐式或显式) 要求被调用方是全部者,使用此选项。

    • 通过引用或常量引用来传递 shared_ptr

      在这样的情况下,引用计数不添加。而且仅仅要调用方不超出范围。被调用方就能够訪问指针。 或者。被调用方能够决定创建一个基于引用的 shared_ptr。从而成为一个共享全部者。

      当调用者并不知道被被调用方。或当您必须传递一个 shared_ptr,并希望避免因为性能原因的复制操作。请使用此选项。

    • 通过底层的指针或引用底层的对象。

      这使得被调用方使用对象。但不使共享全部权或扩展生存期。假设被调用方从原始指针创建一个 shared_ptr,则新的 shared_ptr 是独立于原来的,且没有控制底层的资源。 当调用方和被调用方之间的协定中明白规定调用者保留shared_ptr 生存期的全部权,则使用此选项。

    • 当您决定怎样传递一个 shared_ptr时,确定被调用方是否有共享基础资源的全部权。一个“全部者”就是仅仅要它须要就能够使用底层资源的对象或函数。 假设调用方必须保证被调用方能够在其(函数)生存期以外扩展指针的生存期,请使用第一个选项。 假设您不关心被调用方是否扩展生存期,则通过引用传递并让被调用方复制它。

    • 假设不得不同意帮助程序函数訪问底层指针。而且您知道帮助程序函数将使用指针且在调用函数返回前先返回,则该函数不必共享底层指针的全部权。

      不过在调用方的 shared_ptr 的生存期内同意訪问指针。在这样的情况下,通过引用来传递 shared_ptr,通过原始指针或引用的基本对象都是安全的。

      通过此方式提供一个小的性能改进。而且还有助于表示程序的意图。

    • 有时。比如在一个 std:vector<shared_ptr<T>>中。您可能必须对传递每一个shared_ptr 给lambda表达式体或命名函数对象。 假设lambda或函数没有存储指针。则通过引用传递shared_ptr,以避免调用拷贝构造函数的每一个元素。


  • 相关阅读:
    Linux_文件系统、磁盘分区_RHEL7
    Linux_LVM、RAID_RHEL7
    Linux_LVM、RAID_RHEL7
    Linux_系统时间管理
    简单聊聊HDFS RBF第二阶段工作近期的一些进展
    LinkedBlockingQueue和ArrayBlockingQueue之间的比较
    LinkedBlockingQueue和ArrayBlockingQueue之间的比较
    公司如何使用开源软件
    公司如何使用开源软件
    ListenableFuture和CompletableFuture简单小结
  • 原文地址:https://www.cnblogs.com/yjbjingcha/p/7020072.html
Copyright © 2011-2022 走看看