<三>智能指针之__sharePtr

share_ptr解析

  1. 共享所有权
  2. 存储成本较裸指针多了引用计数指针(和想关控制块-共享)
  3. 接口慎用(蔓延问题)
  4. 线程安全,引用结束增减会减慢多核性能
  5. 最适合共享的不变数据
  6. 支持拷贝构造,支持移动

share_ptr内存模型图
在这里插入图片描述

创建share_ptr易犯错的代码

  Widget* p=new Widget();
  shared_ptr<Widget> spw(p);
  shared_ptr<Widget> spw2(p); 
  // share_ptr都指向了p ,但是引用计数块不是共享的,各自的,spw2为1 , spw也为1 spw2释放了内存后,spw还会继续释放相同内存P

  //应该写成
  shared_ptr<Widget> spw2(spw)
  

关于继承场景下,使用share_ptr的代码注意点


auto b1=std::make_shared<Base>();//父类
b1->process();

auto s1=std::make_shared<Sub>();//子类
s1->process();

share_Ptr<Base> b2{b1};//可以, 发生拷贝构造
share_Ptr<Base> s2{s1};//可以, 发生拷贝构造

share_Ptr<Base> b2=std::static_point_cast<Base>(s1);//OK 子类智能指针初始化父类智能指针推荐做法
share_ptr<Base> s22{s1}; //ok 但更推荐写法是这样 shared_ptr<Base> s22=std::static_point_cast<Base>(s1);

//取出b2指向的真实指针,再做dynamic_cast<Sub*>转型,再基于这个创建share_Ptr,
//程序编译没问题,运行会挂掉, s3 和 b1指向同一块内存,但引用计数是各自的,造成同时释放同一块内存,这个是平时可能写的错误代码
share_ptr<Sub> s3=share_Ptr<Sub>(dynamic_cast<Sub*>b2.get());//错误写法,要小心!!!

//注意:智能指针在.get()方法的时候要非常小心,你最多只能使用裸指针,不要去delete这个裸指针,也不要用这个裸指针封装到另一个之智能指针里面

//怎么安全的将父类的share_ptr转为子类的share_ptr??
shared_ptr<Sub> s22=std::dynamic_point_cast<Sub>(b2);//ok ,共享内存,引用计数共享

unique_ptr 和 share_ptr 存在转换关系,unique_ptr 可以转成share_ptr, 反之不可以

std::unique_ptr<std::string> foo(){
   return std::make_unique<std::string>("testFoo");
}

//相当于 unique_ptr 交出裸指针,释放所有权,share_ptr 基于裸指针创建
std::share_ptr<std::string> sp1=foo(); 

std::unique_ptr<std::string> up1=std::make_unique<std::string>("test");
std::share_ptr<std::string>  sp1=std::move(up1);

关于share_Ptr 与weak_ptr 的话题

问题代码1

class A
{

  int dataA_;
  std::share<B> ptrb_;
};

class B
{
  int dataB_
  std::share<A> ptra_;
};

int main{

    std::share_ptr<A> ptra=std::make_share<A>();
    std::share_ptr<B> ptrb=std::make_share<B>();

    ptra->ptrb_ =ptrb;
    ptrb->ptra_ =ptra;

}
//出现问题? 及问题解析 如下

在这里插入图片描述
如上图所示,main函数栈结束的时候,
第一步 ptrb 先销毁,从上图中可以看到除了自己指向了对象B, 堆上还有一个指针指向了这个B
所以这个对象B 引用计数是2,当ptrb退出main函数栈 ,计数减1,计数还剩1,所以B对象没有被销毁
第二步:ptra销毁,ptra销毁的时候发现除了自己指向对象A,还有个堆对象指向了对象A,
所以这个对象A 引用计数也是2,当ptra退出main函数栈 ,计数减1,计数还剩1,A对象没有被销毁
所以照成两个堆上的对象都没有释放掉,这个就是循环引用问题

问题代码2如下

class A
{
  int dataA_;
  std::share<B> ptrb_;
};

class B
{
  int dataB_
  std::share<C> ptrc_;
};

class C
{
  int dataB_
  std::share<A> ptra_;
};

int main{

    std::share_ptr<A> ptra=std::make_share<A>();
    std::share_ptr<B> ptrb=std::make_share<B>();
    std::share_ptr<C> ptrc=std::make_share<C>();

    ptra->ptrb_ =ptrb;
    ptrb->ptrc_ =ptrc;
    ptrc->ptra_ =ptra;

}
内存结构如图像, 堆上的A,B,C 都无法释放

在这里插入图片描述

下面我们看看如何通过弱引用上面循环引用内存泄漏的解决

#include "iostream"
#include "memory"

class B;

class A{

public:
    int data;

    A(){

      std::cout<<"A()对象构造"<<std::endl;

    }
    ~A(){
        std::cout<<"~A()析构"<<std::endl;
    }
    std::shared_ptr<B>  ptrb_;
private:



};

class B{

public: 

    int data;

    B(){

      std::cout<<"B()对象构造"<<std::endl;

    }
    ~B(){
        std::cout<<"~B()析构"<<std::endl;
    }
    std::weak_ptr<A>  ptra_; //弱引用
};


void play(){

    std::shared_ptr<A> ptra=std::make_shared<A>();
   
    std::shared_ptr<B> ptrb=std::make_shared<B>();

    ptra->ptrb_=ptrb;

    ptrb->ptra_=ptra;

}

int  main(){

    play();

    return 1;
}

//执行结果

A()对象构造
B()对象构造
~A()析构
~B()析构


看内存结构图如下

过程解析

play()函数栈销毁过程
第1: std::shared_ptr<B> ptrb 栈对象先销毁, 栈对象销毁的时候,share_ptr析构函数中发现引用计数为2,所以
就只能完成 引用计数减一操作, 堆对象B没能得到释放

第2: std::shared_ptr<A> ptra 栈对象准备销毁,栈对象销毁的时候,因为
B指向A的是个弱引用,share_ptr析构函数中发现引用计数为1,所以堆上A对象准备先析构销毁,
而A对象中还有个成员 std::shared_ptr<B>  ptrb_;,这个对象也要准备销毁,调用shared_ptr的析构函数,这个时候
对象B的计数是1,所以这个时候 ~B()析构


在这里插入图片描述

关于enalbe_shared_from_this<>问题

问题代码1

class Widget{

    int m_x;
    int m_y;
    int m_z;

public:

    Widget(int x,int y , int z): m_x(x), m_y(y),m_z(z)
    {}

    void print(){
        cout<<m_x<<","<<m_y<<","<<m_z<<endl;
    }

    unique_ptr<Widget> getWidget()
    {
         return unique_ptr<Widget>(this);
    }

    ~Widget()
    {
        cout<<"Widget dtor"<<endl;
    }

};

    Widget* p=new Widget(1,2,3);
    unique_ptr<Widget> w2{p};
    unique_ptr<Widget> w3=p->getWidget(); //--》等价 getWidget(p)

   //上面代码问题
   //使用同一个堆对象内存创建了多个 unique_ptr,导致会多次销毁同一个对象
   //所以不要 unique_ptr<Widget>(this); 这么做!!!!!!!!!!

问题代码2

class Widget {

public:
   
    Widget()
    {
        cout<<"Widget()"<<endl;
    }


    shared_ptr<Widget> getWidget() {

        return shared_ptr<Widget>(this); 

    }

    // shared_ptr<Widget> getWidget(Widget* this) {

    //     return shared_ptr<Widget>(this); 
    // }

   
    void print(){
        cout<<"print"<<endl;
    }

    ~Widget()
    {
        cout<<"~Widget()"<<endl;
    }

    void invoke(){
        process(shared_ptr<Widget>(this));
    }

};

void process( std::shared_ptr<Widget> tp)
{
    tp->print();
    cout<<"ref count: "<<tp.use_count()<<endl;
    cout<<"process--------"<<endl;
}


int main() {

    shared_ptr<Widget> sw1;
    {
        Widget* pw=new Widget();
        sw1=shared_ptr<Widget>(pw);//
        shared_ptr<Widget> sw2=sw1->getWidget(); // getWidget(pw);  
    }

}

上面代码   
return shared_ptr<Widget>(this); 
sw1=shared_ptr<Widget>(pw);//
shared_ptr<Widget> sw2=sw1->getWidget(); // getWidget(pw);  

return shared_ptr<Widget>(this); 的问题同一个堆对象,创建了两个share_ptr
但是这两个share_ptr的引用监控 不是同一个,各自的,两套引用计数控制,所以会导致重复删除!!!

代码3

如果遇到以下场景,怎么应对?

void invoke(){
     process(shared_ptr<Widget>(this));
}
void process( std::shared_ptr<Widget> tp)
{
    tp->print();
    cout<<"ref count: "<<tp.use_count()<<endl;
    cout<<"process--------"<<endl;
}

**怎么解决上述三段代码的问题? enalbe_shared_from_this<> **

class Widget;

void process(const std::shared_ptr<Widget> tp);


//1. 必须公有继承,确保Widget可转型为enable_shared_from_this
//2. 必须使用Shared指针,调用 shared_from_this()
//   安全做法:将构造函数private禁止创建栈对象、使用静态工厂函数确保创建shared指针

class Widget : public std::enable_shared_from_this<Widget> {
public:
    
    std::shared_ptr<Widget> getWidget() {

        // if(weak_from_this().expired())
        // {
        //     cout<<"oops, expired..."<<endl;
        // }
         return shared_from_this(); // OK

         //return shared_ptr<Widget>(this);
    }

    void invoke(){
        process(shared_from_this());
        //process(shared_ptr<Widget>(this));错误!
    }


    void print(){
        cout<<"print"<<endl;
    }

    ~Widget()
    {
        cout<<"~Widget()"<<endl;
    }

    //工厂函数
    static std::shared_ptr<Widget> create() {
        return std::shared_ptr<Widget>(new Widget());
    }

private:
    Widget()=default;//这个避免用户创建栈对象 
   // 这个避免用户创建栈对象,因为栈对象的时候 shared_from_this(),智能指针封装的栈对象指针,不允许,
   // 所以要屏蔽默认构造函数,必须通过工厂方法 获得 堆对象的智能指针,针对就是下面三行代码的情景
   //  Widget w1;
   //  shared_ptr<Widget> w2=w1.getWidget();// &w1
   //  cout<<"ref count: "<<w2.use_count()<<endl;

};

void process(const std::shared_ptr<Widget> tp)
{
    tp->print();
    cout<<"ref count: "<<tp.use_count()<<endl;
    cout<<"process--------"<<endl;
}



int main() {
    {
        //  Widget* p=new Widget();
        //  shared_ptr<Widget> sp1 {p};

        //  auto sp2 = sp1->getWidget(); //sp2 is a copy of sp1
        //shared_ptr<Widget> sp2 {p};
        // process(sp1);
        // cout<<"ref count: "<<sp1.use_count()<<endl;
        // cout<<"ref count: "<<sp2.use_count()<<endl;

    }

    {
        //  Widget w1;
        //  shared_ptr<Widget> w2=w1.getWidget();// &w1
        //  cout<<"ref count: "<<w2.use_count()<<endl;

        //  Widget* p=new Widget();// w1;
        //  shared_ptr<Widget> w2=p->getWidget();// &w1
        //  delete p;
    }
    {

         shared_ptr<Widget> sp1=Widget::create();

        
         //auto sp2=sp1->getWidget();
         cout<<"ref count: "<<sp1.use_count()<<endl;
         cout<<"ref count: "<<sp2.use_count()<<endl;

         cout<< sizeof(Widget)<<endl;

    }

}

最后我们通过VS编译器看看 share_ptr内的内存结构
简单代码

#include "iostream"
#include "memory"
class student {

public:

    student(int age_):age(age_) {

        std::cout << "student 构造函数 堆内存地址" << (int*)this << std::endl;

    }

    student(const student& stu) {

        std::cout << "stu拷贝构造" << std::endl;
        this->age = stu.age;

    }
    ~student() {

        std::cout << "析构" << std::endl;

    }

    void test() {

        std::cout << "student test()>>" << age << std::endl;
    }

    int age;
private:



};



int main() {

    student* p = new student(999);

    std::shared_ptr<student> ptr(p);

    std::cout << sizeof(ptr) << std::endl;

    int* p1 = (int*)(&ptr);
  
    std::cout << (int *)*p1 << std::endl; // 取出的就是student对象堆地址

    if (p1 == nullptr) {

        std::cout << "null" << std::endl;
    }
    else {
        //转成student 指针,
        student* s = (student*)(*p1);
        s->test();
    }

    p1++;//定位到控制块指针
    std::cout << (int *)*p1 << std::endl; //控制块地址

    return 1;
}

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
智能指针share_ptrC++11标准库中的一种用于管理动态分配内存的智能指针。它能够自动管理资源,在不需要时释放内存,防止内存泄漏。然而,在实际代码中,我们常常会遇到相互引用的情况,即两个或多个对象之间产生循环引用,导致内存泄漏。这种情况对于传统的裸指针来说是无法解决的,但对于智能指针share_ptr来说,则可以通过以下方法解决: 1.使用weak_ptr:在循环引用的情况下,一个对象可以通过weak_ptr来共享相应的资源,而不会增加资源的引用计数。因此,在处理相互引用问题时,可以使用weak_ptr来打破循环引用,避免内存泄漏。 2.使用单独的管理对象:在某些情况下,多个对象之间的相互引用较为复杂,使用weak_ptr不能完全解决问题,例如多层嵌套的相互引用。这时候,可以引入一个额外的管理对象,用于管理这些对象之间的关系,从而减少相互引用的复杂度。 3.使用裸指针代替智能指针:虽然智能指针share_ptr可以很好地管理内存,但在某些场景下,使用裸指针可能会更为简单、清晰。例如,在处理循环引用时,可以使用裸指针进行判断,然后手动删除内存。 综上所述,智能指针share_ptrC++中非常有用的工具,在避免内存泄漏方面发挥了很大的作用。虽然在处理相互引用时可能会遇到一些麻烦,但使用上述方法可以很好地解决问题,保证代码质量和性能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值