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

    C++ 11 智能指针

    前言:

      近来,学习STL,突然发现有智能指针,做了一周的学习(工作之外的时间),断断续续的学习,特此做下记录。

    诞生的原因:

       为了防止内存泄露,和二次释放的问题。无非就是嫌弃自己管理内存太费劲,可以写个更简单管理堆内存的类。

    利用C++的特性:

       类结束会调用析构函数,无非就是栈空间出栈,同时释放掉动态创建的空间。

    智能指针的作用:

      将指针封装成类,利用了一种叫RAII(资源获取即初始化的技术,听着有点高大上),重载操作符(->和*),行为表现的像指针

    1. 防止多次释放该指针,导致崩溃(前提是这个指针被你释放是否赋值为空指针,如果赋值为空,就没有所谓的崩溃问题,习惯决定代码的健壮性)
    2. 智能指针作用把值语义转为引用(总是搞个二传手,这就是所谓的安全,降低性能为代价,在内存无比大的今天,随意了)

    C++ 11 中的智能指针:

      包含在头文件<memory>中,分别有三个智能指针,分别为shared_ptr,unique_ptr,weak_ptr。

    1. 介绍下shared_ptr(别人写的很好,我只做网络的搬运工,来丰富自己的知识体系)

        1)原理:shared_ptr的多个对象指向同一个指针(大多是new出来的空间指针),该指针使用引用计数,每使用一次,内部计数器加1,每析构一次,内部的引用计数器减1,减为0的时候,自动删除指向的堆内存。

        2)实现:就是一个模板类,没事的时候强烈建议看下里面的具体实现,挺有意思的。

        3)不要用一个原始指针初始化多个shared_ptr,否则会造成二次释放同一内存

         4)注意避免循环使用,会在下面举例,并讲解。

      2.unique_ptr

        “唯一”拥有所指对象,同一时刻只能有一个unique_ptr指向给定对象(禁止使用拷贝语义,只能用移动语义将其移动)。对比原始指针,也是利用了RAII的特性。用户可以定义delete操作。

    #include <iostream>
    #include <memory>
    using namespace std;
    
    int main()
    {
              unique_ptr<int> uptr(new int(10)); //初始化
              unique_ptr<int> uptr1 = move(uptr); //转移所有权
              uptr2.release();  //释放所有权
              return 0;  
    }

       3.weak_ptr,配合shared_ptr而引入的的智能指针,是弱引用,相对于shared_ptr强引用来说的。看似就像一个观察者,观测资源的使用情况。weak_ptr可以从一个shared_ptr获取另一个weak_ptr来构造,获取资源的观察权 。 并不会引起计数加的情况,成员有use_count(),查看资源的引用计数,expired(),判断是否指向的资源被释放, 当返回为true的时候,这个资源的引用计数为0,相当于被释放,反之就没有被释放掉。lock(),返回当前分享指 针,计数器并加1.

    #include <iostream>
    #include <memory>
    
    using namespace std;
    int main()
    {
                shared_ptr<int> s_ptr = make_shared<int>(10);
                cout<<s_ptr.use_count() <<endl;
                weak_ptr<int> wp(s_ptr);
                cout<<wp.use_count() <<endl;
                if(!wp.expired())
                {
                       shared_ptr s_ptr2 = wp.lock();  //引用计数器加1
                       *s_ptr = 100;
                       cout<<wp.use_count()<<endl;
                 }    
    }    
    
    运行结果:
    1
    1
    2

    应用场景及其问题:

      1.循环引用,场景:考虑一个简单的场景--家长和孩子,一个父母有一个孩子,一个孩子有一双父母。

      使用原始指针的实现:

      

    #include <iostream>
    using namespace std;
    
    class Child;
    class Parent;
    
    class Parent 
    {
    private:
        Child* myChild;
    public:
        void setChild(Child* ch)
        {
            this->myChild = ch;
        }
        void doSomething() 
        {
            if(this->myChild)
            {
                cout<<"Child alive"<<endl;
            }
        }
        ~Parent() {
            cout<<"delete myChild"<<endl;
            delete myChild;    
    }
    };
    
    class Child
    {
    private:
        Parent* myParent;
    public:
        void setParent(Parent* p)
        {
            this->myParent = p;
        }
        void doSomething() {
            if(this->myParent)
            {
                
                cout<<"myParent alive"<<endl;                                    }
        }
        ~Child() {
        cout<<"delete myParent"<<endl;
        delete myParent;    
    }
    };
    
    
    int main()
    {
        Parent* p = new Parent;
        Child* c = new Child;
        p->setChild(c);
        c->setParent(p);
        delete c;
        return 0;
    }

      如何使用智能指针解决该问题呢:引入智能指针,两个类只要保证一个类是shared_ptr(强引用)一个是weak_ptr(弱引用)

    #include <memory>
    #include <iostream>
    class Child; class Parent; class Parent{ private: std::weak_ptr<Child> ChildPtr; public: void setChild(std::shared_ptr<Child> child) { this->ChildPtr = child; } void doSomething() { } ~Parent() {} }; class Child { private: std::shared_ptr<Parent> ParentPtr; public: void setParent(std::shared_ptr<Parent> parent) { this->ParentPtr = parent; } void doSomething() {} ~Child() {} }; int main() { std::weak_ptr<Parent> wpp; std::weak_ptr<Child> wpc; { std::shared_ptr<Parent> p(new Parent); std::shared_ptr<Child> c(new Child); p->setChild(c); c->setParent(p); wpp = p; wpc = c; std::cout<<p.use_count() <<std::endl; std::cout<<c.use_count() <<std::endl; } std::cout <<wpp.use_count() << std::endl; std::cout << wpc.use_count() << std::endl; return 0; }
    运行结果:
    2
    1
    0
    0
    注意如果使用g++编译,请添加参数-std=c++11(弱引用是C++11引入)

    2..返回shared_ptr本身,并不引起计数器加+1

    #include<iostream>
    #include<memory>
    
    using namespace std;
    
    class Test: public enable_shared_from_this<Test>
    {
    public:
        Test(){}
        ~Test()
        {cout <<"~Test()"<<endl;}
        shared_ptr<Test> sget()
        {
            return shared_from_this();
        }
    };
    
    int main()
    {
        weak_ptr<Test> wp;
        {
        shared_ptr<Test> sp(new Test);
        wp = sp;
        cout<<"sp is "<<sp.use_count()<<endl;
        sp->sget();
        cout<<"sp is "<<sp.use_count()<<endl;
        }
        cout<<"wp is"<<wp.use_count()<<endl;
        return 0;
    
        return 0;
    }
    运行结果:
    sp is 1
    sp is 1
    ~Test()
    wp is 0

    3.注册销毁函数

    #include<iostream>
    #include<memory>
    
    using namespace std;
    
    struct MyStruct
    {
        int *p;
        MyStruct():p(new int(10)){}
    };
    
    int  main()
    {
        MyStruct st;
        {
            shared_ptr<MyStruct> sp(&st, [](MyStruct *ptr){
                delete(ptr->p);
                ptr->p = nullptr;
                cout<<"destructed"<<endl;
            });
        }
        if(st.p != nullptr)
            cout<<"no destroyed"<<endl;
        else
            cout<<"be destroyed"<<endl;
        return 0;
    }
    运行结果:
    destructed
    be destroyed

    4.线程安全讨论

      官方文档:1)同一个shared_ptr对象可以被多线程同时读取

           2) 不同的shared_ptr对象可以被多线程同时修改(提起12分注意力,这里有坑)

           3)任何其他并发访问的结果都是无定义的(什么软,这个目前无法理解

      对1)来说,都能理解,读一定是安全,当在2)情况下,由于内部shared_ptr有两个成员,一个计数,一个指向

      实际内存的指针,具体内部实现也没有上锁,同时操作两个数据成员,读写操作无法做到原子化, 在多线程编程           中,在多个线程同时访问同一个shared_ptr的时           候,请加mutex保护。

      下面来详细的分析下为什么:

                        1)首先看下shared_ptr内存结构,加入该指针指向一个Foo的类

                          

              

             2)考虑一个简单的场景,有三个shared_ptr<Foo> 对象 x, g,n;

           

           shared_ptr<Foo> g(new Foo);   //线程之间共享的shared_ptr 

                            shared_ptr<Foo> x; //线程A的局部变量

           shared_ptr<Foo> n(new Foo);//线程B的局部变量

           开始:还挺整齐的,符合我们的预想

           

                线程A执行x = g;即(read g),以下图示:但是还没来的急将引用计数+1,切换到线程B

                 

                            同时线程B执行g=n;(即写g),如下图

                         

                           这个时候就已经将Foo1申请的动态内存归还给操作系统了,出现空悬指针,如下图:

                        

                          最后将回到线程A,如下图:

                          

                        现在这个状态,整个人都不好了。

                        多线程无保护的读写,造成了"x空悬指针"的后果,综上,论证为啥对shared_ptr读写要加锁的原因。

    以上就是对智能指针的理解

    具体参考:http://www.cnblogs.com/gqtcgq/p/7492772.html

                      vs2010中关于C++11 智能指针的源码

                                                                                                          23:37:57  2019-04-26

    The future's not set,there is no fate but what we make for ourselves.
  • 相关阅读:
    pipelinewise 学习二 创建一个简单的pipeline
    pipelinewise 学习一 docker方式安装
    Supercharging your ETL with Airflow and Singer
    ubuntu中使用 alien安装rpm包
    PipelineWise illustrates the power of Singer
    pipelinewise 基于singer 指南的的数据pipeline 工具
    关于singer elt 的几篇很不错的文章
    npkill 一个方便的npm 包清理工具
    kuma docker-compose 环境试用
    kuma 学习四 策略
  • 原文地址:https://www.cnblogs.com/wang1994/p/10765974.html
Copyright © 2011-2022 走看看