• 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.
  • 相关阅读:
    通过shell脚本排查jar包中类冲突
    批量复制及执行命令shell脚本
    java String hashCode遇到的坑
    hive常用命令
    hadoop-2.10.0安装hive-2.3.6
    centos7安装mysql-5.7.28
    centos7安装mysql-5.5和mysql-5.6
    centos7搭建hadoop2.10高可用(HA)
    centos7搭建hadoop2.10完全分布式
    kafka(一)-为什么选择kafka
  • 原文地址:https://www.cnblogs.com/wang1994/p/10765974.html
Copyright © 2020-2023  润新知