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

 

posted @ 2019-04-26 23:50  努力飞的小菜鸟  阅读(7765)  评论(0编辑  收藏  举报