C++智能指针

一 智能指针

  智能指针是一个类,这个类的构造函数中传入一个普通指针,然后开辟空间,析构函数中释放传入的指针。智能指针的类都是栈上的对象,所以当函数(或程序)结束时会自动被释放,
  使用注意点:

  • 所有的智能指针类都有一个explicit构造函数,以指针作为参数。因此不能自动将指针转换为智能指针对象,必须显式调用:
 shared_ptr<double> pd; 
double *p_reg = new double;
pd = p_reg;                               // not allowed (implicit conversion)
pd = shared_ptr<double>(p_reg);           // allowed (explicit conversion)
shared_ptr<double> pshared = p_reg;       // not allowed (implicit conversion)
shared_ptr<double> pshared(p_reg);        // allowed (explicit conversion)
  • 对全部智能指针都应避免的一点:
string vacation("I wandered lonely as a cloud.");
shared_ptr<string> pvac(&vacation);   // No

  pvac过期时,程序将把delete运算符用于非堆内存,这是错误的。

二 智能指针发展史

智能指针都具有RAII的思想,即构造函数获得资源,析构函数清理资源,但是当用一个智能指针拷贝构造另一个智能指针的时候,有可能会有浅拷贝的问题,这个空间会被释放多次,智能指针的发展就是围绕着指针拷贝问题而走。

1 auto_ptr

  C++98里面有一个智能指针auto_ptr。

2 scoped_ptr/shared_ptr

因为auto_ptr有缺陷,但是C++标准里面从C++98到C++11之间没有出现新的智能指针能解决这个缺陷,所以在这段时间内,boost这个官方组织就增加了智能指针(scoped_ptr,shared_ptr,weak_ptr等)

  1. scoped_ptr采用防拷贝的方式(防拷贝就是不允许拷贝,拷贝就会出错;防拷贝的实现:将拷贝构造和的赋值运算符重载只声明不实现,并且声明为私有);
  2. shared_ptr为共享指针,里面采用引用计数,当有shared_ptr指向同一块空间的时候就增加引用计数,当引用计数减为0的时候才释放该智能指针管理的那块空间。
  3. 但是shared_ptr有一个缺点,就是会出现循环引用的问题(当一个shared_ptr(如sp1)管理的空间里面包含一个shared_ptr的指针(_next),另一个shared_ptr(如sp2)管理的空间里面也包含一个shared_ptr指针(_prev)时,当sp1->_next = sp2;sp2->_prev = sp1;此时就会使得sp1和sp2的引用计数都变为2,当出了这个作用域sp1和sp2的引用计数都会减为1,但是只有引用计数为0时才会释放管理的空间,就会使得sp1和sp2管理的空间没有释放。
  4. 所以利用weak_ptr来解决循环引用的问题,weak_ptr叫弱指针,它主要是为了配合shared_ptr使用,用来解决循环引用的问题;
    将会出现循环引用问题的指针用weak_ptr保存着,weak_ptr并不拥有这块空间,所以weak_ptr里面不增加shared_ptr的引用计数,也不会释放这块空间。(注意weak_ptr里面也有自己的引用计数)
  5. boost库里面还包含scoped_array和shared_array(这个适用于delete[]的场景)

3 C++11(unique_ptr和shared_ptr、weak_ptr)

  1. C++11借鉴了boost库里面的智能指针(C++对应的智能指针位于头文件里。auto_ptr是C++98提供的解决方案,C+11已将将其摒弃,并提出了unique_ptr作为auto_ptr替代方案。
    C++11里面的unique_ptr就是boost库里面的scoped_ptr(防拷贝);
    C++11里面的shared_ptr就是boost里面的shared_ptr。
  2. C++11里面不包含类似于scoped_array和shared_array,而它采用定制删除器的方式管理空间的释放。定制删除器就是自己指定采用何种方式释放该空间(delete/free或其它);因为在实现智能指针的过程中,我们需要管理数据的构造和析构,但不同的数据有不同的析构方式,就需要自己指定删除方式。(例如new出来的数据,就必须用delete,而new[ ]就需要delete[ ]等)。注意:boost库里面也包含定制删除器。

三 STL中的智能指针

  使用:包含头文件<memory>

1 auto_ptr(C++98)

  1. C++98里面有一个智能指针auto_ptr,对于拷贝构造和赋值运算符重载,该智能指针采用管理权转移的方式(当一个指针拷贝构造另一个指针时,当前指针就将对空间的管理权交给拷贝的那个指针,当前指针就指向空);
  2. 但是这种方式不符合指针的要求(可以允许多个指针指向同一块空间,将一个指针赋值给另一个指针的时候,就是需要让两个指针指向同一块空间,而auto_ptr只允许一块空间上只能有一个指针指向它),并且当管理权转移之后要想再访问之前的指针,就会出错,因为之前的指针已经为NULL,就会出现解引用空指针的问题。
      一般的用法是:
std::auto_ptr<server_t> server = std::auto_ptr<server_t>(new server_t());

  示例:

auto_ptr< string> ps (new string ("I reigned lonely as a cloud.”);
auto_ptr<string> vocation; 
vocaticn = ps;

  上述赋值语句将完成什么工作呢?如果ps和vocation是常规指针,则两个指针将指向同一个string对象。这是不能接受的,因为程序将试图删除同一个对象两次——一次是ps过期时,另一次是vocation过期时。要避免这种问题,方法有多种:

  • 定义陚值运算符,使之执行深复制。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的副本,缺点是浪费空间,所以智能指针都未采用此方案。
  • 建立所有权(ownership)概念。对于特定的对象,只能有一个智能指针可拥有,这样只有拥有对象的智能指针的构造函数会删除该对象。然后让赋值操作转让所有权。这就是用于auto_ptr和uniqiie_ptr 的策略,但unique_ptr的策略更严格。
  • 创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。例如,赋值时,计数将加1,而指针过期时,计数将减1,。当减为0时才调用delete。这是shared_ptr采用的策略。

2 unique_ptr(C++11)

  unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权,unique_ptr还可能没有对象,这种情况被称为empty。

//智能指针的创建  
unique_ptr<int> u_i; //创建空智能指针
u_i.reset(new int(3)); //"绑定”动态对象  
unique_ptr<int> u_i2(new int(4));//创建时指定动态对象
unique_ptr<T,D> u(d);   //创建空unique_ptr,执行类型为T的对象,用类型为D的对象d来替代默认的删除器delete

//所有权的变化  
int *p_i = u_i2.release(); //释放所有权  
unique_ptr<string> u_s(new string("abc"));  
unique_ptr<string> u_s2 = std::move(u_s); //所有权转移(通过移动语义),u_s所有权转移后,变成“空指针” 
u_s2.reset(u_s.release());//所有权转移
u_s2=nullptr;//显式销毁所指对象,同时智能指针变为空指针。与u_s2.reset()等价
unique_ptr<string> p3 (new string ("auto");   //#1
unique_ptr<string> p4;                       //#2
p4 = p3;                                      //#3

  编译器认为语句#3非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全。
  但unique_ptr还有更聪明的地方。有时候,会将一个智能指针赋给另一个并不会留下危险的悬挂指针。假设有如下函数定义:

unique_ptr<string> demo(const char * s)
{
    unique_ptr<string> temp (new string (s))return temp;
}

  并假设编写了如下代码:

unique_ptr<string> ps;
ps = demo('Uniquely special")

  demo()返回一个临时unique_ptr,然后ps接管了原本归返回的unique_ptr所有的对象,而返回时临时的 unique_ptr 被销毁,也就是说没有机会使用 unique_ptr 来访问无效的数据,换句话来说,这种赋值是不会出现任何问题的,即没有理由禁止这种赋值。实际上,编译器确实允许这种赋值,这正是unique_ptr更聪明的地方。
  C++有一个标准库函数std::move(),能够将一个unique_ptr赋给另一个。下面是一个使用前述demo()函数的例子,该函数返回一个unique_ptr对象,使用move后,原来的指针仍转让所有权变成空指针,可以对其重新赋值。

unique_ptr<string> ps1, ps2;
ps1 = demo("hello");
ps2 = move(ps1);
ps1 = demo("alexia");
cout << *ps2 << *ps1 << endl;

3 shared_ptr(C++11)

  shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针,当然这需要额外的开销:
  (1)shared_ptr 对象除了包括一个所拥有对象的指针外,还必须包括一个引用计数代理对象的指针;
  (2)时间上的开销主要在初始化和拷贝操作上, *和->操作符重载的开销跟auto_ptr是一样;
  (3)开销并不是我们不使用shared_ptr的理由,,永远不要进行不成熟的优化,直到性能分析器告诉你这一点。
  1.基本使用与boost里shared_ptr的使用方式一致

void Test_Shared_ptr()
{
      std::shared_ptr<int> sp1(new int(20));
      std::shared_ptr<int> sp2(sp1);
      std::cout << *sp2 << std::endl;
}

  2.由于C++11里面没有shared_array或scoped_array之类智能指针,所以只能用shared_ptr需要自己定制删除器:
  (1)首先自己定制删除器(例如我定制了一个delete和一个delete[ ])
  自己需要编写相应的仿函数,在用shared_ptr时不用将自己编写的删除器作为模板参数,但是在构造shared_ptr对象时需要传相应仿函数的对象。

template<class T>
struct Delete
{
      void operator()(T* ptr)
      {
           delete ptr;
      }
};

template<class T>
struct DeleteArray
{
      void operator()(T* ptr)
      {
           delete[] ptr;
      }
};

  (2)使用:

void Test_Shared_ptr()
{
      std::shared_ptr<int> sp1(new int(20));
      std::shared_ptr<int> sp2(sp1);
      std::cout << *sp2 << std::endl;

      DeleteArray<std::string> da;
      std::shared_ptr<std::string> sp3(new std::string[10],da); 

}

4 weak_ptr(C++11)

  weak_ptr 被设计为与 shared_ptr 共同工作,可以从一个 shared_ptr 或者另一个 weak_ptr 对象构造而来。weak_ptr 是为了配合 shared_ptr 而引入的一种智能指针,它更像是 shared_ptr 的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载operator*和->,因此取名为weak,表明其是功能较弱的智能指针。它的最大作用在于协助shared_ptr工作,可获得资源的观测权,像旁观者那样观测资源的使用情况。
  使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于use_count()==0,但更快,表示被观测的资源(也就是shared_ptr管理的资源)已经不复存在。weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr管理的对象, 从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr。总结来说,weak_ptr的基本用法总结如下:

weak_ptr<T> w;      //创建空weak_ptr,可以指向类型为T的对象。
weak_ptr<T> w(sp);  //与shared_ptr指向相同的对象,shared_ptr引用计数不变。T必须能转换为sp指向的类型。
w=p;                //p可以是shared_ptr或weak_ptr,赋值后w与p共享对象。
w.reset();          //将w置空。
w.use_count();      //返回与w共享对象的shared_ptr的数量。
w.expired();        //若w.use_count()为0,返回true,否则返回false。
w.lock();           //如果expired()为true,返回一个空shared_ptr,否则返回非空shared_ptr。 
#include < assert.h>

#include <iostream>
#include <memory>
#include <string>
using namespace std;
int main()
{
    shared_ptr<int> sp(new int(10));
    assert(sp.use_count() == 1);
    weak_ptr<int> wp(sp); //从shared_ptr创建weak_ptr
    assert(wp.use_count() == 1);
    if (!wp.expired())//判断weak_ptr观察的对象是否失效
    {
        shared_ptr<int> sp2 = wp.lock();//获得一个shared_ptr
        *sp2 = 100;
        assert(wp.use_count() == 2);
    }
    assert(wp.use_count()== 1);
    cout<<"int:"<<*sp<<endl;
    return 0;
}

  weak_ptr的作用:其实weak_ptr可用于打破循环引用。引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引用的对象。

#include <iostream>  
#include <memory>  

class Woman;  
class Man
{  
private:  
    //std::weak_ptr<Woman> _wife;  
    std::shared_ptr<Woman> _wife;  
public:  
    void setWife(std::shared_ptr<Woman> woman)
    {  
        _wife = woman;  
    }  

    void doSomthing()
    {  
        if(_wife.lock())
        {  
        }  
    }  

    ~Man()
    {
        std::cout << "kill man\n";  
    }  
};  

class Woman
{  
private:  
    //std::weak_ptr<Man> _husband;  
    std::shared_ptr<Man> _husband;  
public:  
    void setHusband(std::shared_ptr<Man> man)
    {  
        _husband = man;  
    }  
    ~Woman()
    {  
        std::cout <<"kill woman\n";  
    }  
};  

int main(int argc, char** argv)
{  
    std::shared_ptr<Man> m(new Man());  
    std::shared_ptr<Woman> w(new Woman());  
    if(m && w)
    {  
        m->setWife(w);  
        w->setHusband(m);  
    }  
    return 0;  
}

  在Man类内部会引用一个Woman,Woman类内部也引用一个Man。当一个man和一个woman是夫妻的时候,他们直接就存在了相互引用问题。man内部有个用于管理wife生命期的shared_ptr变量,也就是说wife必定是在husband去世之后才能去世。同样的,woman内部也有一个管理husband生命期的shared_ptr变量,也就是说husband必须在wife去世之后才能去世。这就是循环引用存在的问题:husband的生命期由wife的生命期决定,wife的生命期由husband的生命期决定,最后两人都死不掉,违反了自然规律,导致了内存泄漏。
  一般来讲,解除这种循环引用有下面三种可行的方法:
  (1)当只剩下最后一个引用的时候需要手动打破循环引用释放对象。
  (2)当parent的生存期超过children的生存期的时候,children改为使用一个普通指针指向parent。
  (3)使用弱引用的智能指针打破这种循环引用。
  weak_ptr对象引用资源时不会增加引用计数,但是它能够通过lock()方法来判断它所管理的资源是否被释放。做法就是上面的代码注释的地方取消注释,取消Woman类或者Man类的任意一个即可,也可同时取消注释,全部换成弱引用weak_ptr。
  另外很自然地一个问题是:既然weak_ptr不增加资源的引用计数,那么在使用weak_ptr对象的时候,资源被突然释放了怎么办呢?呵呵,答案是你根本不能直接通过weak_ptr来访问资源。那么如何通过weak_ptr来间接访问资源呢?答案是:在需要访问资源的时候weak_ptr为你生成一个shared_ptr,shared_ptr能够保证在shared_ptr没有被释放之前,其所管理的资源是不会被释放的。创建shared_ptr的方法就是lock()方法。
  另外很自然地一个问题是:既然weak_ptr不增加资源的引用计数,那么在使用weak_ptr对象的时候,资源被突然释放了怎么办呢?呵呵,答案是你根本不能直接通过weak_ptr来访问资源。那么如何通过weak_ptr来间接访问资源呢?答案是:在需要访问资源的时候weak_ptr为你生成一个shared_ptr,shared_ptr能够保证在shared_ptr没有被释放之前,其所管理的资源是不会被释放的。创建shared_ptr的方法就是lock()方法。
  注意:shared_ptr实现了operator bool() const方法来判断一个管理的资源是否被释放。

四 Boost中的智能指针

boost中有scoped_ptrscoped_arrayshared_ptrshared_arrayweak_ptr这5种类型。

1 scoped_ptr

  1.源代码:

template<class T> 
class scoped_ptr 
{
private:

    T * px;    //只含有一个成员变量T*的指针

     //将拷贝构造与赋值运算符重载声明为私有,且不实现
    scoped_ptr(scoped_ptr const &);
    scoped_ptr & operator=(scoped_ptr const &);

    typedef scoped_ptr<T> this_type;

    void operator==( scoped_ptr const& ) const;
    void operator!=( scoped_ptr const& ) const;

public:

    typedef T element_type;

     //构造函数
    explicit scoped_ptr( T * p = 0 )
          : px( p ) 
    {
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
        boost::sp_scalar_constructor_hook( px );
#endif
    }

#ifndef BOOST_NO_AUTO_PTR

    explicit scoped_ptr( std::auto_ptr<T> p ) BOOST_NOEXCEPT 
         : px( p.release() )
    {
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
        boost::sp_scalar_constructor_hook( px );
#endif
    }

#endif

     //析构函数
    ~scoped_ptr() 
    {
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
        boost::sp_scalar_destructor_hook( px );
#endif
        boost::checked_delete( px );
    }

    void reset(T * p = 0) 
    {
        BOOST_ASSERT( p == 0 || p != px ); 
        this_type(p).swap(*this);
    }

    T & operator*() const 
    {
        BOOST_ASSERT( px != 0 );
        return *px;
    }

    T * operator->() const 
    {
        BOOST_ASSERT( px != 0 );
        return px;
    }

     //获得原生指针
    T * get() const BOOST_NOEXCEPT
    {
        return px;
    }

#include <boost/smart_ptr/detail/operator_bool.hpp>

    void swap(scoped_ptr & b) BOOST_NOEXCEPT
    {
        T * tmp = b.px;
        b.px = px;
        px = tmp;
    }

  2.使用:

#include<iostream>
#include<string>
#include<boost/scoped_ptr.hpp>
#include<boost/scoped_array.hpp>

int main()
{
      boost::scoped_ptr<int> sp1(new int(10));

      //boost::scoped_ptr<int> sp2(sp1);     //不能拷贝

      boost::scoped_array<std::string> sp2(new std::string[10]);

      return 0;
}

  scoped_ptr:scoped_ptr和auto_ptr的特点完全一样。虽然说socped_ptr不能赋值拷贝,但也有用处,但另一缺点是它不能管理数组,很多情况我们都需要在函数内部动态申请内存,等函数返回时释放内存。

2 socped_array

  socped_array的特点就在于此,它可以管理连续的地址空间,而在离开作用域时自动释放。需要注意的是socped_array仍然不能用于容器或者函数间传递,因为它仍然没有实现计数引用。

3 shared_ptr

  1.使用:

void Test_boost_shared_ptr()
{
      boost::shared_ptr<int> sp1(new int(10));
      std::cout << *sp1 << std::endl;    //输出10

      boost::shared_ptr<int> sp2(sp1);
      std::cout << *sp2 << std::endl;    //输出10

      boost::shared_array<std::string> sp3(new std::string[10]);
      sp3[5] = "111";     //shared_array里面重载了[],所以可以采用下标的方式进行读写

      boost::shared_array<std::string> sp4(sp3);
      std::cout << sp4[5] << std::endl;   //输出111
}

  较之于socped_ptr,shared_ptr的特点在于它可以赋值拷贝,内部有一个引用计数器,只有当计数器等于0时才析构内存,它内部重载了=运算符,所以说shared_ptr可用做容器元素,正如名字一样,shared_ptr——共享。

4 shared_array

  shared_array:可以这么说,shared_array = socped_arrayr + shared_ptr,怎么说呢。。。就是说shared_ptr既可以用来管理连续地址空间,又可以在函数见传递,或者是用于容器中。

5 week_ptr

  week_ptr:shared_ptr的引用技术很好的解决了复制拷贝问题,但是这些都是01问题,要么能拷贝复制,要么不能拷贝复制,那假如我想在有些情况下需要拷贝复制,有些情况下不需要拷贝复制呢?比如说循环引用,再比如说基类中的指针总不能用计数器吧。所以说这就是week_ptr的用处,week_ptr可以对shared_ptr进行引用而不会引起其计数器增加。

五 智能指针的线程安全性

  对于智能指针对象本身而言,其不是线程安全性的,底层没有提供加锁机制,但是其可以用来作为线程的安全性检测:
它的引用计数本身是安全且无锁的,但对象的读写则不是,因为 shared_ptr 有两个数据成员,读写操作不能原子化。
  shared_ptr/weak_ptr 的线程安全级别与 std::string 和STL 容器一样,都不是线程安全的。
  对于 weak_ptr 在提升为 shared_ptr 的时候是线程安全的。
  当一个对象暴漏给多线程时,如何保证正确析构?使用 weak_ptr 保存对象就可以解决竞态条件
  技术陷阱:
shared_ptr 技术与陷阱
意外延长对象的生命期
shared_ptr 是强引用(“铁丝”绑的),只要有一个指向x 对象的 shared_ptr 存在,该对象就不会析构。而 shared_ptr 又是允许拷贝构造和赋值的(否则引用计数就无意义了),如果不小心遗留了一个拷贝,那么对象就永世长存了。例如前面提到如果把p. 16 中L48 observers_ 的类型改为vector<shared_ptr >,那么除非手动调用unregister(),否则Observer 对象永远不会析构。即便它的析构函数会调用unregister(),但是不去unregister() 就不会调用Observer 的析构函数,这变成了鸡与蛋的问题。这也是Java 内存泄漏的常见原因。
另外一个出错的可能是boost::bind,因为boost::bind 会把实参拷贝一份,如果参数是个shared_ptr,那么对象的生命期就不会短于boost::function 对象:

class Foo
{
	void doit();
};
shared_ptr<Foo> pFoo(new Foo);
boost::function<void()> func = boost::bind(&Foo::doit, pFoo); // long life foo

这里func 对象持有了shared_ptr 的一份拷贝,有可能会在不经意间延长倒数第二行创建的Foo 对象的生命期。

函数参数
因为要修改引用计数(而且拷贝的时候通常要加锁), shared_ptr 的拷贝开销比拷贝原始指针要高,但是需要拷贝的时候并不多。多数情况下它可以以const reference 方式传递,一个线程只需要在最外层函数有一个实体对象,之后都可以用const reference 来使用这个shared_ptr。例如有几个函数都要用到Foo 对象:

void save(const shared_ptr<Foo>& pFoo); // pass by const reference
void validateAccount(const Foo& foo);
bool validate(const shared_ptr<Foo>& pFoo) // pass by const reference
{
	validateAccount(*pFoo);
	// ...
}

那么在通常情况下,我们可以传常引用(pass by const reference):

void onMessage(const string& msg)
{
	shared_ptr<Foo> pFoo(new Foo(msg)); // 只要在最外层持有一个实体,安全不成问题
	if (validate(pFoo))
	{ // 没有拷贝pFoo
		save(pFoo); // 没有拷贝pFoo
	}
}

遵照这个规则,基本上不会遇到反复拷贝 shared_ptr 导致的性能问题。另外由于pFoo 是栈上对象,不可能被别的线程看到,那么读取始终是线程安全的.

析构动作在创建时被捕获
这是一个非常有用的特性,这意味着:
•虚析构不再是必需的。
•shared_ptr 可以持有任何对象,而且能安全地释放。
shared_ptr 对象可以安全地跨越模块边界,比如从DLL 里返回,而不会造成
从模块A 分配的内存在模块B 里被释放这种错误。
•二进制兼容性,即便Foo 对象的大小变了,那么旧的客户代码仍然可以使用新
的动态库,而无须重新编译。前提是Foo 的头文件中不出现访问对象的成员的
inline 函数,并且Foo 对象的由动态库中的Factory 构造,返回其shared_ptr。
•析构动作可以定制。
最后这个特性的实现比较巧妙,因为shared_ptr 只有一个模板参数,而“析构行为”可以是函数指针、仿函数(functor)或者其他什么东西。这是泛型编程和面向对象编程的一次完美结合
  析构所在的线程对象的析构是同步的,当最后一个指向x 的shared_ptr 离开
其作用域的时候,x 会同时在同一个线程析构。这个线程不一定是对象诞生的线程。这个特性是把双刃剑:如果对象的析构比较耗时,那么可能会拖慢关键线程的速度(如果最后一个 shared_ptr 引发的析构发生在关键线程);同时,我们可以用一个单独的线程来专门做析构,通过一个 BlockingQueue<shared_ptr<void>> 把对象的析构都转移到那个专用线程,从而解放关键线程。
   shared_ptr 是管理共享资源的利器,需要注意避免循环引用,通常的做法是owner 持有指向child 的shared_ptr,child 持有指向owner 的weak_ptr。
综上:智能指针shared_ptr本身不是线程安全的,当他暴漏给多线程时,读(智能指针对象之间的拷贝)写(对象的析构)仍然需要加锁来保证安全性(一般使用互斥锁就够了,无需读写锁,因为临界区非常小,通常只有一个语句(赋值等等)),但是对于它的内部的引用计数的操作以及由weak_ptr提升为shared_ptr时却是线程安全的。
  解决循环引用方法:使用者保存shared_ptr,在child(类中)使用weak_ptr封装指针。

六 对比

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

  • 有一个指针数组,并使用一些辅助指针来标示特定的元素,如最大的元素和最小的元素;
  • 两个对象包含都指向第三个对象的指针;
  • STL容器包含指针。

  2、如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。如果函数使用new分配内存,并返还指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。可将unique_ptr存储到STL容器在那个,只要不调用将一个unique_ptr复制或赋给另一个算法(如sort())。
  智能指针的交叉(循环)引用问题?造成什么结果?怎么解决?
  循环引用造成了资源无法释放

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值