动态内存管理和智能指针 2.0 -- shared_ptr

原创 2017年09月11日 20:54:16

shared_ptr出现原因

通过第一章的学习,我们知道不管是auto_ptr合适scoped_ptr都是存在缺陷的,于是我们必须想出一个方法既能很好的管理我们的内存,而且在使用的时候,可以多个指针指向一个内存,这个时候就出现了shared_ptr。

shared_ptr的实现原理

shared_ptr使用的引用计数的浅拷贝的形式,这个时候是不需要使用引用计数的写时拷贝的,因为多个指针指向的是同一个动态的内存空间,当其中的一个内存空间改变的时候,其他的内容也是相应的改变的。
这个时候我们的shared_ptr这个类的成员变量中就要有一个指针,用于指向一个动态开辟的存储空间,还需要有一个用于计数的指针,这个指针指向一个动态开辟的内存空间,一般是整型的,这个整型 变量中存放的是我们指向同一个空间的个数,然后这个动态的整型空间只在构造函数的使用开辟出来,其他的拷贝构造函数还有赋值运算符的重载 的时候直接的使用了。

简单代码实现

template<class T>
class Shared_Ptr
{
public:
    Shared_Ptr(T* ptr)
        :_ptr(ptr)
        ,_count(new int(1))
    {
    }

    Shared_Ptr(const Shared_Ptr& ptr)
        :_ptr(ptr._ptr)
        , _count(ptr._count)
    {
        (*_count)++;
    }
    Shared_Ptr& operator=(const Shared_Ptr& ptr)
    {
        if (this == &ptr)
        {
            return *this;
        }

        else
        {
            if (!--(*_count))
            {
                delete _ptr;
                delete _count;
                cout << "delete older" << endl;
            }
            _ptr = ptr._ptr;
            _count = ptr._count;
            (*_count)++;
        }
        return *this;
    }

    ~Shared_Ptr()
    {
        (*_count)--;
        if ((*_count) == 0)
        {
            delete _ptr;
            delete _count;
            cout << "delete" << endl;
        }
    }

    T& operator*()
    {
        return *_ptr;
    }

    T* operator->()
    {
        return _ptr;
    }


private:
    T* _ptr;
    int* _count;
};

代码分析

构造函数
当我们使用构造函数的时候,这个时候肯定是已经动态开辟了一个内存空间的,所以我们这个时候也给我们_count指针动态的开辟一个空间,并且这个空间的值是1,因为此时一定是有一个空间了。

拷贝构造函数
拷贝构造函数的时候,我们是让一个已经存在的对象去初始化另一个对象,所以这个时候我们只需要让当前对象的指针指向那个动态的空间,同时时当前对象的计数指针也指向那个对象的计数空间,并且使当前对象的计数值加1,因为这个时候已经有两个对象 指向了一个动态的空间了。

析构函数
析构函数的时候,我们需要把引用计数减一,这个时候再去判断我们的引用计数的值是否为0,如果是0,这个时候就需要释放我们的动态管理的空间,同时释放掉我们的引用计数的动态空间,同时需要把这两个指针置为NULL。

赋值运算符的重载

该检查的是,这个赋值是不是自赋值,如果是 自赋值,这个时候直接返回该对象 即可了,还需要注意的是,我们 的引用计数不需要加1.如果不是自赋值,此时需要把当前对象的引用计数减1,同时判断减1之后应用计数是不是为0,如果是0则需要释放掉,如果不是0,就不要管了,然后接下来把我们当前指针指向被赋值的那个空间即可。

循环引用和weak_ptr

当下面的代码的时候会出现一种情况就是 循环引用,首先看代码,下面的代码是我们定义的一个结构体

struct Str
{
    Shared_Ptr<Str> _prev;
    Shared_Ptr<Str> _next;
};

再来看我们的构造函数

Shared_Ptr(T* ptr)
        :_ptr(ptr)
        ,_count(new int(1))
    {
    }

上面的构造函数中,我们的构造函数没有缺省值,会报错,因为我们在定义_prev和_next的时候,没有传入参数,所以我们需要把我们的构造函数改成下面的样子,就是把缺省值赋值为NULL。
这个时候我们写一个测试用例

Shared_Ptr<Str> a = new Str;
Shared_Ptr<Str> b = new Str;

这个时候会打印出六个delete,因为我们new出来的两个对象本身是有两个对象的,所以析构的时候,会析构这里面的两个一个是_next一个是_prev,然后我们的a和b本身也是指向对象的,所以一共析构了六次。

隐患问题 – 循环引用

如果我们是像下面的方式 使用它,就会出现循环 引用的问题,请看下面的代码


    Shared_Ptr<Str> a = new Str;
    Shared_Ptr<Str> b = new Str;


    a->_next = b;
    b->_prev = a;

结合刚刚的分析,我们来分析上面的一段程序,首先是a和b分别指向了两个new出来的对象 ,然后这两个对象 里面的next和prev分别指向了两个对象,接着 执行a->_next = b;
b->_prev = a;的时候,会调用赋值运算符的重载,然后就是a里面的next由原来的内容指向了b,b里面 的prev由原来的内容指向了a;这个时候问题就来了,b这个指向指向的内存空间有两个指针指向着,一个是b自己,一个是a->_next,所以析构的时候不会释放内存空间,这不是我们想看到的结果。也可以这样子分析,就是我们的a和b析构的时候,只是 把引用计数减1,接下来析构a->_next和b->prev的时候,都是相互依赖彼此的,所以都释放不了,这就是循环引用。

weak_ptr

于是为了解决上面的循环引用的特殊场景,配合着shared_ptr设计出了一个weak_ptr,代码如下

#include<iostream>
using namespace std;

template<class T>
class Weak_ptr;

template<class T>
class Shared_Ptr
{

public:
    friend class Weak_ptr<T>;
    Shared_Ptr(T* ptr = NULL)
        :_ptr(ptr)
        ,_count(new int(1))
    {
    }

    Shared_Ptr(const Shared_Ptr& ptr)
        :_ptr(ptr._ptr)
        , _count(ptr._count)
    {
        (*_count)++;
    }

    Shared_Ptr& operator=(Shared_Ptr& ptr)
    {
        if (this == &ptr)
        {
            return *this;
        }

        else
        {
            if (!--(*_count))
            {
                delete _ptr;
                delete _count;
                cout << "delete older" << endl;
            }
            _ptr = ptr._ptr;
            _count = ptr._count;
            (*_count)++;
        }
        return *this;
    }

    ~Shared_Ptr()
    {
        (*_count)--;
        if ((*_count) == 0)
        {
            delete _ptr;
            delete _count;
            _ptr = NULL;
            _count = NULL;
            cout << "delete" << endl;
        }
    }

    T& operator*()
    {
        return *_ptr;
    }

    T* operator->()
    {
        return _ptr;
    }


protected:
    T* _ptr;
    int* _count;
};

template<class T>
class Weak_ptr
{
public:
    Weak_ptr()
        :_ptr(NULL)
    {}
    Weak_ptr(Shared_Ptr<T> ptr)
    {
        _ptr = ptr._ptr;
    }
    T* operator->()
    {
        return _ptr;
    }

private:
    T* _ptr;
};


struct Str
{
    Weak_ptr<Str> _prev;
    Weak_ptr<Str> _next;
    int _a;
};


void TestPtr()
{


    Shared_Ptr<Str> a = new Str;
    Shared_Ptr<Str> b = new Str;

    a->_next = b;
    b->_prev = a;
    a->_next->_a = 0;
    cout << b->_a;

}

首先我们看Weak_ptr,它的成员变量是T* _ptr;此时我们的Str那个自定义的结构体中的指针就可以改成了Weak_ptr的形式,因为我们的Weak_ptr维护的是一个普通的指针,但是我们在使用的时候需要用到_next指向一个Shared_ptr的对象,所以这个时候,我们不要把Weak_ptr的拷贝构造函数的参数写成是Shared_ptr,然后赋值的时候,需要把Shared_ptr的_ptr赋值给Weak_ptr的_ptr,但是Shared_ptr中的_ptr是 私有的,所以这个时候我们在Shared_ptr里面把Weak_ptr声明为友元。但是这个时候又出现了一个 问题就是,我们的他声明为友元之后,编译器找不到我们的友元类,因为我们的Weak_ptr的定义部分是在Shared_ptr的后面,所以这个时候,需要在Shared_ptr的前面声明我们的Weak_ptr。

定制防函数

所谓的防函数就是让我们的类看起来像是函数一样
举一个简单的例子,看下面的代码

struct Compare
{
    bool operator()(int a,int b)
    {
        return a > b;
    }
};

void test()
{
    Compare com;
    cout << com(1,2);
}

类Compare是我们定制的一个防函数,下面的test就是我们把他 当成一个函数来使用它

为什么要引入防函数呢,因为我们在使用的时候上面的Shared_ptr的时候,我们管理的内存 空间可能是一个FILE*的一个指针,这个时候我们就不能只使用delete来释放我们的空间,这个时候我们就需要定制一个防函数,把它作为一个参数放在构造函数 中,同时我们的Shared_ptr的成员变量里面需要定义一个这样的变量。

请看下面的代码


#include<iostream>
using namespace std;
#include<assert.h>
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1



struct DelFile
{
    void operator()(FILE* f)
    {
        fclose(f);
        cout << "fclose" << endl;
    }
};


struct DelDel
{
    void operator()(void* p)
    {
        assert(p);
        delete p;
        cout << "delete" << endl;
    }
};

template<class T,class Del>
class Weak_ptr;

template<class T,class Del>
class Shared_Ptr
{

public:
    friend class Weak_ptr<T,Del>;
    Shared_Ptr(T* ptr,Del d)
        :_ptr(ptr)
        ,_count(new int(1))
        , _del(d)
    {
    }

    Shared_Ptr(const Shared_Ptr& ptr)
        :_ptr(ptr._ptr)
        , _count(ptr._count)
    {
        (*_count)++;
    }

    Shared_Ptr& operator=(Shared_Ptr& ptr)
    {
        if (this == &ptr)
        {
            return *this;
        }

        else
        {
            if (!--(*_count))
            {
                del(_ptr);
                delete _count;
                cout << "delete older" << endl;
            }
            _ptr = ptr._ptr;
            _count = ptr._count;
            (*_count)++;
        }
        return *this;
    }

    ~Shared_Ptr()
    {
        (*_count)--;
        if ((*_count) == 0)
        {
            _del(_ptr);
            delete _count;
            _ptr = NULL;
            _count = NULL;
            //cout << "delete" << endl;
        }
    }

    T& operator*()
    {
        return *_ptr;
    }

    T* operator->()
    {
        return _ptr;
    }


protected:
    T* _ptr;
    int* _count;
    Del _del;
};

template<class T, class Del>
class Weak_ptr
{
public:
    Weak_ptr()
        :_ptr(NULL)
    {}
    Weak_ptr(Shared_Ptr<T,Del> ptr)
    {
        _ptr = ptr._ptr;
    }
    T* operator->()
    {
        return _ptr;
    }

private:
    T* _ptr;
};


struct Str
{
    Weak_ptr<Str,DelDel> _prev;
    Weak_ptr<Str, DelDel> _next;
    int _a;
};

void TestFile()
{
    DelFile d;
    Shared_Ptr<FILE, DelFile> a(fopen("w.ss","w"),d);   //注意这里传参的时候,首先要实例化一个对象
            //我一开始使用的是Shared_Ptr<FILE, DelFile> a(fopen("w.ss","w"),DelFile d)
            //这种方式显然是错误的,我不能在一个函数里面去实例化一个对象
}


void TestPtr()
{
    DelDel d;
    Shared_Ptr<Str, DelDel> a(new Str, d);
    Shared_Ptr<Str, DelDel> b(new Str, d);

    a->_next = b;
    b->_prev = a;
    a->_next->_a = 0;
    cout << b->_a;



}

struct Compare
{
    bool operator()(int a,int b)
    {
        return a > b;
    }
};

void test()
{
    Compare com;
    cout << com(1,2);
}




版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

[Leetcode] 321. Create Maximum Number 解题报告

题目: Given two arrays of length m and n with digits 0-9 representing two numbers. Create the m...

【Linux学习笔记】栈与函数调用惯例

原文地址: 栈与函数调用惯例(又称调用约定)— 基础篇         记得一年半前参加百度的校招面试时,被问到函数调用惯例的问题。当时只是懂个大概,比如常见函数调用约定类型及...

C++ Primer : 第十二章 : 动态内存之shared_ptr与new的结合使用、智能指针异常

shared_ptr与new的结合使用、智能指针异常
  • JY_95
  • JY_95
  • 2015年08月26日 00:27
  • 1912

[内存管理]智能指针shared_ptr与工厂函数相结合

shared_ptr很好地消除了显式的delete调用,如果掌握了它的用法,那么可以说,以后delete将会彻底消失在你的编程词典中。 但这还不够,因为shared_ptr的构造还需要new调用,这...
  • ajioy
  • ajioy
  • 2012年03月17日 12:53
  • 3792

动态内存管理与智能指针

C、 C++中编译内存分配://一个 C、 C++程序编译时内存分为 4大存储区:栈区、堆区、数据段、程序代码段。 代码段: 又称为常量区,储存常量和编译之后的代码指令 数据段: 又称为静...

【C++】动态内存管理(四)智能指针(std)

智能指针总结 对于编译器来说,智能指针实际上是一个栈对象,并非指针类型,在栈对象生命期即将结束时,智能指针通过析构函数释放有它管理的堆内存。所有智能指针都重载了“operator->”操作符,直接...

boost中的智能指针shared_ptr的指针管理

最近在阅读《Beyond the C++ STL》一书的shared_ptr一章时,遇到点困惑,记录如下: 原书44~45页 #include "boost/shared_ptr.hpp"...

C++中的资源管理(一):构造自己的auto_ptr与shared_ptr智能指针

C++中指针管理是很频繁的事情,并且很容易出错。要是有方法可以简化这个过程,并减少出错就太棒了。下面提供了几个方法来实现: 1.用类来管理指针 class Car { public: Car(); ...

【C++11新特性】 C++11智能指针之shared_ptr

C++中的智能指针首先出现在“准”标准库boost中。随着使用的人越来越多,为了让开发人员更方便、更安全的使用动态内存,C++11也引入了智能指针来管理动态对象。在新标准中,主要提供了shared_p...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:动态内存管理和智能指针 2.0 -- shared_ptr
举报原因:
原因补充:

(最多只允许输入30个字)