智能指针及RAII

智能指针及RAII

问题

C++中最令人头疼的问题是强迫程序员对申请的资源(文件,内存等)进行管理,一不小心就会出现泄露(忘记对申请的资源进行释放)的问题。

// C++
auto ptr = new std::vector<int>();

//使用了垃圾回收技术,不在需要人为管理,相关的虚拟机会自动释放不需要使用的资源。
// Java
ArrayList<int> list = new ArrayList<int>();
# Python
lst = list()

C++的解决办法:RAII

在传统 C++ 里我们只好使用 newdelete 去『记得』对资源进行释放。而 C++11 引入了智能指针的概念,使用了引用计数的想法,让程序员不再需要关心手动释放内存。

解决思路:

  • 利用C++中一个对象出了其作用域会被自动析构,因此我们只需要在构造函数的时候申请空间,而在析构函数(在离开作用域时调用)的时候释放空间,这样,就减轻了程序员在编码过程中,考虑资源释放的问题,这就是RAII

RAII,完整的英文是 Resource Acquisition Is Initialization,是 C++ 所特有的资源管理方式。

  • 有少量其他语言,如 D、Ada 和 Rust 也采纳了 RAII,但主流的编程语言中, C++ 是唯一一个依赖 RAII 来做资源管理的。
  • RAII 依托栈和析构函数,来对所有的资源——包括堆内存在内——进行管理。对 RAII 的使用,使得 C++ 不需要类似于 Java 那样的垃圾收集方法,也能有效地对内存进行管理。

具体而言,C11的stl中为大家带来了3种智能指针,正确合理的使用可以有效的帮助大家管理资源,当然,在C++的使用智能指针没有像Java,python这种具备垃圾回收机制的语言那么舒适,毕竟,程序员还需要做一些额外的事情,但是,这远比传统的C或者C++更加优雅。

3种智能指针分别是:

  • std::shared_ptr 强指针
  • std::unique_ptr
  • std::weak_ptr 弱指针

在早期有一个auto_ptr,这四种指针在使用上有区别:

  • auto_ptr有缺陷是过时的产物。

  • unique_ptr对auto_ptr的问题进行了修正。

  • shared_ptr使用了引用计数,但是会出现循环引用的问题需要配合后面的weak_ptr一起使用。

auto_ptr

class template

std::auto_ptr

template <class X> class auto_ptr;

Automatic Pointer [deprecated]

从官网的文档上就可以看出,这个auto_ptr指针不推荐使用(deprecated),原因这里也有说明:

Note: This class template is deprecated as of C++11. unique_ptr is a new facility with a similar functionality, but with improved security (no fake copy assignments), added features (deleters) and support for arrays. See unique_ptr for additional information.

解释:auto_ptr指针在c++11标准中就被废除了,可以使用unique_ptr来替代,功能上是相同的,unique_ptr相比较auto_ptr而言,提升了安全性(没有浅拷贝),增加了特性(delete析构)和对数组的支持。

This class template provides a limited garbage collection facility for pointers, by allowing pointers to have the elements they point to automatically destroyed when the auto_ptr object is itself destroyed.

解释:这个类模板提供了有限度的垃圾回收机制,通过将一个指针保存在auto_ptr对象中,当auto_ptr对象析构时,这个对象所保存的指针也会被析构掉。

auto_ptr objects have the peculiarity of taking ownership of the pointers assigned to them: An auto_ptr object that has ownership over one element is in charge of destroying the element it points to and to deallocate the memory allocated to it when itself is destroyed. The destructor does this by calling operator delete automatically.

解释: auto_ptr 对象拥有其内部指针的所有权。这意味着auto_ptr对其内部指针的释放负责,即当自身被释放时,会在析构函数中自动的调用delete,从而释放内部指针的内存。

Therefore, no two auto_ptr objects should own the same element, since both would try to destruct them at some point. When an assignment operation takes place between two auto_ptr objects, ownership is transferred, which means that the object losing ownership is set to no longer point to the element (it is set to the null pointer).

解释:

  • 正因如此,不能有两个auto_ptr 对象拥有同一个内部指针的所有权,因为有可能在某个时机,两者均会尝试析构这个内部指针。

  • 两个auto_ptr对象之间发生赋值操作时,内部指针被拥有的所有权会发生转移,这意味着这个赋值的右者对象会丧失该所有权,不在指向这个内部指针(其会被设置成null指针)。

到这里,我们来看一下auto_ptr的提供的接口和使用方法:

其中构造值得说一下:

Constructs an auto_ptr object either from a pointer or from another auto_ptr object.
Since auto_ptr objects take ownership of the pointer they point to, when a new auto_ptr is constructed from another auto_ptr, the former owner releases it.

解释: auto_ptr的构造的参数可以是一个指针,或者是另外一个auto_ptr对象。

  • 当一个新的auto_ptr获取了内部指针的所有权后,之前的拥有者会释放其所有权。

1.auto_ptr的构造及所有权的转移

#include "stdafx.h"
#include <iostream>
#include <memory>
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
	//通过指针进行构造
	std::auto_ptr<int> aptr(new int(3)); 
	
	printf("aptr %p : %d\r\n", aptr.get(), *aptr);
    
	//这样会编译出错,因为auto_ptr的构造有关键字explicit
	//explicit关键字表示调用构造函数时不能使用隐式赋值,而必须是显示调用
	//std::auto_ptr<int> aptr2 = new int(3); 

	//可以用其他的auto_ptr指针进行初始化
	std::auto_ptr<int> aptr2 = aptr;
	printf("aptr2 %p : %d\r\n", aptr2.get(), *aptr2);

	//但是这么内存访问出错,直接0xc05,因为aptr已经释放了其所有权。
	//*aptr = 4;
	printf("aptr %p\r\n", aptr.get());
	
	return 0;
}

2.auto_ptr析构及资源的自动释放

void foo_release()
{
	//释放
	int* pNew = new int(3);
	{
		std::auto_ptr<int> aptr(pNew);
	}

}
  • 这里显然,当出了块作用域之后,aptr对象会自动调用析构,然后在析构中会自动的delete其内部指针,也就是出了这个作用域后,其内部指针就被释放了。

  • 当然上面这种写法是不推荐的,因为我们这里本质上就是希望不去管理指针的释放工作,上面的写法就又需要程序员自己操心指针的问题,也就是使用智能指针要避免出现指针的直接使用

在这里可以在使用前调用release,从而放弃其内部指针的使用权,但是同样这么做违背了智能指针的初衷。

void foo_release()
{
	//释放
	int* pNew = new int(3);
	{
		std::auto_ptr<int> aptr(pNew);
		int* p = aptr.release();
	}

}

3.分配新的指针所有权

可以调用reset来重新分配指针的所有权,reset中会先释放原来的内部指针的内存,然后分配新的内部指针。

void foo_reset()
{
	//释放
	int* pNew = new int(3);
	int*p = new int(5);
	{
		std::auto_ptr<int> aptr(pNew);
		aptr.reset(p);

	}
}

4.=运算符的使用

void foo_assign()
{
	std::auto_ptr<int> p1;
	std::auto_ptr<int> p2;

	p1 = std::auto_ptr<int>(new int(3));
	*p1 = 4;
	p2 = p1;
}

auto_ptr存在的问题

为什么11标准会不让使用auto_ptr,原因是其使用有问题。

1. 作为参数传递会存在问题。

因为有拷贝构造和赋值的情况下,会释放原有的对象的内部指针,所以当有函数使用的是auto_ptr时,调用后会导致原来的内部指针释放。

void foo_test(std::auto_ptr<int> p)
{
	printf("%d\r\n", *p);
}

int _tmain(int argc, _TCHAR* argv[])
{
	std::auto_ptr<int> p1 = std::auto_ptr<int>(new int(3));
	foo_test(p1);

	//这里的调用就会出错,因为拷贝构造函数的存在,p1实际上已经释放了其内部指针的所有权了
	printf("%d\r\n", *p1);
	
	return 0;
}

2. 不能使用vector数组

因为数组的实现,所以这么定义会出错:

void foo_ary()
{
	std::vector<std::auto_ptr<int>> Ary;
	std::auto_ptr<int> p(new int(3));
	Ary.push_back(p);

	printf("%d\r\n", *p);

}

unique_ptr

前面我们讲解了auto_ptr的使用及为什么会被C++11标准抛弃,接下来,我们来学习unique_ptr的使用:

unique_ptr提供了以下操作:在这里插入图片描述

看起来似乎与auto_ptr相似,但是其实有区别。

1. 构造函数

虽然这里的构造函数比较多,但是可以发现,实际上是没有类似auto_ptr的那种拷贝构造:

void foo_constuct()
{
    //这样构造是可以的
    std::unique_ptr<int> p(new int(3));

    //空构造
    std::unique_ptr<int> p4;

    //下面三种写法会报错
    std::unique_ptr<int> p2 = p;
    std::unique_ptr<int> p3(p);
    p4 = p;

}

因此,这就从根源上杜绝了auto_ptr作为参数传递的写法了。

2. reset

reset的用法和auto_ptr是一致的:

void foo_reset()
{
    //释放
    int* pNew = new int(3);
    int*p = new int(5);
    {
        std::unique_ptr<int> uptr(pNew);
        uptr.reset(p);

    }
}

3.release

release与reset一样,也不会释放原来的内部指针,只是简单的将自身置空。

void foo_release()
{
    //释放
    int* pNew = new int(3);
    int* p = NULL;
    {
        std::unique_ptr<int> uptr(pNew);
        p = uptr.release();
    }
}

4.move

但是多了个move的用法:

void foo_move()
{
    int* p = new int(3);
    
    std::unique_ptr<int> uptr(p);
    std::unique_ptr<int> uptr2 = std::move(uptr);
    
}

因为unique_ptr不能将自身对象内部指针直接赋值给其他unique_ptr,所以这里可以使用std::move()函数,让unique_ptr交出其内部指针的所有权,而自身置空,内部指针不会释放。

5.数组

可以采用move的方法来使用数组。

直接使用仍然会报错:

void foo_ary()
{
    std::vector<std::unique_ptr<int>> Ary;
    std::unique_ptr<int> p(new int(3));
    Ary.push_back(p);

    printf("%d\r\n", *p);

}

但是可以采用move的办法,这样就编译通过了:

void foo_ary()
{
    std::vector<std::unique_ptr<int>> Ary;
    std::unique_ptr<int> uptr(new int(3));
    Ary.push_back(std::move(uptr));

    printf("%d\r\n", *uptr);

}

但是因为uptr的语义,所以作为参数传递了, 转移了内部指针的所有权,原来的uptr就不能使用了。

所以综上,unique_ptr指的是只有一个对象拥有指针的所有权,可以转移,但是不能直接赋值或者拷贝构造。

所有示例代码如下:

// testUniqueptr.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include <memory>
#include <vector>

void foo_constuct()
{
    //这样构造是可以的
    std::unique_ptr<int> p(new int(3));

    //空构造
    std::unique_ptr<int> p4;

    //下面三种写法会报错
//  std::unique_ptr<int> p2 = p;
//  std::unique_ptr<int> p3(p);
//  p4 = p;

}

void foo_reset()
{
    //释放
    int* pNew = new int(3);
    int*p = new int(5);
    {
        std::unique_ptr<int> uptr(pNew);
        uptr.reset(p);

    }
}

void foo_release()
{
    //释放
    int* pNew = new int(3);
    int* p = NULL;
    {
        std::auto_ptr<int> uptr(pNew);
        p = uptr.release();
    }
}



void foo_move()
{
    int* p = new int(3);
    std::unique_ptr<int> uptr(p);
    std::unique_ptr<int> uptr2 = std::move(uptr);
}

void foo_ary()
{
    std::vector<std::unique_ptr<int>> Ary;
    std::unique_ptr<int> uptr(new int(3));
    Ary.push_back(std::move(uptr));

    printf("%d\r\n", *uptr);

}


int _tmain(int argc, _TCHAR* argv[])
{
    foo_ary();




    return 0;
}


shared_ptr与weak_ptr

shared_ptr是带引用计数的智能指针:

1. 构造

其初始化多了一种写法:std::make_shared

void foo_construct()
{
    int* p = new int(3);

    std::shared_ptr<int> sptr(p);
    std::shared_ptr<int> sptr2(new int(4));
    std::shared_ptr<int> sptr3 = sptr2;
    std::shared_ptr<int> sptr4 = std::make_shared<int>(5);
}

这里显然可以看到有引用计数的存在。

通过修改上面例子种的sptr3的作用域,可以发现,出了块作用域之后,shared_ptr对应的引用计数的值减少了。

void foo_construct()
{
    int* p = new int(3);

    std::shared_ptr<int> sptr(p);
    std::shared_ptr<int> sptr2(new int(4));
    {
        std::shared_ptr<int> sptr3 = sptr2;
    }
    
    std::shared_ptr<int> sptr4 = std::make_shared<int>(5);

}

2. 注意事项:

  1. 如果用同一个指针去初始化两个shared_ptr时,则引用计数仍然会出错:
void foo_test()
{
    int* p = new int(3);

    {
        std::shared_ptr<int> sptr(p);

        {
            std::shared_ptr<int> sptr2(p);
        }
    }
}

显然出了最里面的作用域之后,sptr2对象就已经释放了,此时,对于sptr2来说,p的引用计数为0,所有p被释放,但是实际上sptr还存在,所以再释放sptr时,就会0xc0000005.

  1. shared_ptr最大的问题是存在循环引用的问题:

如果两个类的原始指针的循环使用,那么会出现重复释放的问题:

class CPerson;
class CSon;

class Cperson
{
public:
    Cperson(){
        
    }

    void Set(CSon* pSon){
        m_pSon = pSon;
    }
    
    ~Cperson(){
        if (m_pSon != nullptr)
        {
            delete m_pSon;
            m_pSon = nullptr;
        }
    }

    CSon* m_pSon;
};

class CSon
{
public:
    CSon(){

    }

    void Set(Cperson* pParent){
        m_pParent = pParent;
    }

    ~CSon(){
        if (m_pParent != nullptr)
        {
            delete m_pParent;
            m_pParent = nullptr;
        }
    }

    Cperson* m_pParent;
};


int _tmain(int argc, _TCHAR* argv[])
{
    Cperson* pPer = new Cperson();
    CSon* pSon = new CSon();

    pPer->Set(pSon);
    pSon->Set(pPer);

    delete pSon;

    return 0;
}

这里,delete pSon会出现循环的调用父子类的析构函数,问题很大。

因此,这里考虑使用引用计数的shared_ptr来实现。

#pragma once

#include <memory>
class CPerson;
class CSon;

class Cperson
{
public:
    Cperson(){

    }

    void Set(std::shared_ptr<CSon> pSon){
        m_pSon = pSon;
    }

    ~Cperson(){
    }

    std::shared_ptr<CSon> m_pSon;
};

class CSon
{
public:
    CSon(){

    }

    void Set(std::shared_ptr<Cperson> pParent){
        m_pParent = pParent;
    }

    ~CSon(){
    }

    std::shared_ptr<Cperson> m_pParent;
};
void testShared()
{
    CSon* pSon = new CSon();
    Cperson* pPer = new Cperson();

    {
        std::shared_ptr<Cperson> shared_Parent(pPer);
        std::shared_ptr<CSon> shared_Son(pSon);

        shared_Parent->Set(shared_Son);
        shared_Son->Set(shared_Parent);

        printf("pSon : use_count = %d\r\n", shared_Son.use_count());
        printf("pPer : use_count = %d\r\n", shared_Parent.use_count());
    }


}

这里在出作用域后发现,实际上两个对象均未被销毁:

最后两者的引用计数均为1,原因是出了块作用域之后,两个shared_parent和shared_son均会析构,在这两个智能指针的内部,均会先去判断对应的内部指针是否-1是否为0,显然这里相互引用的情况下,引用计数初值为2,减1后值为1,所以两个指针均不会被释放。

这里,其实只需要一个释放了,另外一个也能跟着释放,可以采用弱指针,即人为的迫使其中一个引用计数为1,从而打破闭环。

这里只需要将上例子中的任意一个强指针改为弱指针即可。

举例:

最后的结果:

此时,两个内部指针均会得到释放。

原因是,弱指针的引用不会增加原来的引用计数,那么就使得引用不再是闭环,所以在出作用域之后,全部得到释放。

weak_ptr的使用

  1. weak_ptr本身并不具有普通内部指针的功能,而只是用来观察其对应的强指针的使用次数。

  2. 因此,这里弱指针的在使用上,实际上是一个特例,即不增加引用计数也能获取对象,因此,实际上在使用弱指针时,不能通过弱指针,直接访问内部指针的数据,而应该是先判断该弱指针所观察的强指针是否存在(调用expired()函数),如果存在,那么则使用lock()函数来获取一个新的shared_ptr来使用对应的内部指针。

  3. 实际上,如果不存在循环引用,就不需要使用weak_ptr了,这种做法仍然增加了程序员的负担,所以不如java c#等语言垃圾回收机制省心。

void testWeak()
{
    std::shared_ptr<int> sharedPtr(new int(3));
    std::weak_ptr<int> weakPtr(sharedPtr);


    printf("sharedPtr_Count = %d, weakPtr_Count = %d, Value = %d \r\n", sharedPtr.use_count(), weakPtr.use_count(), *sharedPtr);
    //当weakPtr为空或者对应的shared_ptr不再有内部指针时,expired返回为true.
    if (!weakPtr.expired())
    {
        std::shared_ptr<int> sharedPtr2 = weakPtr.lock();
        printf("sharedPtr_Count = %d, weakPtr_Count = %d, Value = %d \r\n", sharedPtr.use_count(), weakPtr.use_count(), *sharedPtr);
        *sharedPtr2 = 5;
    }

    printf("sharedPtr_Count = %d, weakPtr_Count = %d, Value = %d \r\n", sharedPtr.use_count(), weakPtr.use_count(), *sharedPtr);
}

智能指针的循环引用

// TestC11.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include <cstring>

using namespace std;

//智能指针:
// 1. 用起来像指针
// 2. 会自己对资源进行释放

class CStudent
{
public:
    CStudent() {}

    void test() {
        cout << "CStudent" << endl;
        m_nSex = 1;
    }

private:
    char* m_pszBuf;
    int   m_nSex;
};


template<typename T>
class CSmartPtr;

template<typename T>
class CRefCount
{
    friend class CSmartPtr<T>;
public:
    CRefCount(T* pStu) {
        m_pObj = pStu;
        m_nCount = 1;
    }

    ~CRefCount() {
        delete m_pObj;
        m_pObj = NULL;
    }

    void AddRef() {
        m_nCount++;
    }

    void Release() {
        if (--m_nCount == 0) {
            //这么写就表示自己一定要是一个堆对象
            delete this;
        }
    }

private:
    T* m_pObj;
    int       m_nCount;
};

template<typename T>
class CSmartPtr
{
public:

    CSmartPtr()
    {
        m_pRef = NULL;
    }

    CSmartPtr(T* pStu)
    {
        m_pRef = new CRefCount<T>(pStu);
    }

    ~CSmartPtr()
    {
        if (m_pRef != NULL){
            m_pRef->Release();
        } 
    }

    CSmartPtr(CSmartPtr& obj)
    {
        m_pRef = obj.m_pRef;
        m_pRef->AddRef();
    }

    CSmartPtr& operator=(CSmartPtr& obj)
    {
        if (m_pRef == obj.m_pRef) {
            return *this;
        }

        if (m_pRef != NULL)
        {
            m_pRef->Release();
        }

        m_pRef = obj.m_pRef;
        m_pRef->AddRef();

        return *this;
    }

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

    T* operator->()
    {
        return m_pRef->m_pObj;
    }

    T** operator&()
    {
        return &m_pRef->m_pObj;
    }

    T& operator*()
    {
        return *m_pRef->m_pObj;
    }

    operator T*()
    {
        return m_pRef->m_pObj;
    }

private:
    CRefCount<T>* m_pRef;
};

class B;

class A
{

public:
    A() {}
    CSmartPtr<B> m_b;
};

class B
{

public:
    B() {}
    CSmartPtr<A> m_a;
};


int main(int argc, char* argv[])
{
    {
        CSmartPtr<A> a = new A;
        CSmartPtr<B> b = new B;
        a->m_b = b;
        b->m_a = a;
    }

    return 0;
}

深入分析shared_ptr与weak_ptr的实现

stl中使用了shared_ptr来管理一个对象的内部指针,并且使用了weak_ptr来防止前面所提到的shared_ptr循环引用的问题。

接下来简单的分析shared_ptr和weak_ptr的实现,最后通过自己写代码来模拟shared_ptr和weak_ptr,达到深入学习的目的:

测试代码如下:

#include "stdafx.h"
#include <memory>

int _tmain(int argc, _TCHAR* argv[])
{   
    std::shared_ptr<int> sptr(new int(3));
    std::shared_ptr<int> sptr2 = sptr;

    std::weak_ptr<int> wptr = sptr;

    if (!wptr.expired()){
        
        std::shared_ptr<int> sptr3 = wptr.lock();
    }

    return 0;
}
  1. 首先看直接看继承关系和类成员:

shared_ptr与weak_ptr均继承自同一个父类 _Ptr_base

template<class _Ty>
    class shared_ptr
        : public _Ptr_base<_Ty>
    {   // class for reference counted resource management
public:
    typedef shared_ptr<_Ty> _Myt;
    typedef _Ptr_base<_Ty> _Mybase;

    shared_ptr() _NOEXCEPT
        {   // construct empty shared_ptr
        }

    template<class _Ux>
        explicit shared_ptr(_Ux *_Px)
        {   // construct shared_ptr object that owns _Px
        _Resetp(_Px);
        }

    template<class _Ux,
        class _Dx>
        shared_ptr(_Ux *_Px, _Dx _Dt)
        {   // construct with _Px, deleter
        _Resetp(_Px, _Dt);
        }

    shared_ptr(nullptr_t)
        {   // construct empty shared_ptr
        }

    _Myt& operator=(_Myt&& _Right) _NOEXCEPT
        {   // construct shared_ptr object that takes resource from _Right
        shared_ptr(_STD move(_Right)).swap(*this);
        return (*this);
        }

    template<class _Ty2>
        _Myt& operator=(shared_ptr<_Ty2>&& _Right) _NOEXCEPT
        {   // construct shared_ptr object that takes resource from _Right
        shared_ptr(_STD move(_Right)).swap(*this);
        return (*this);
        }

    ~shared_ptr() _NOEXCEPT
        {   // release resource
        this->_Decref();
        }

    _Myt& operator=(const _Myt& _Right) _NOEXCEPT
        {   // assign shared ownership of resource owned by _Right
        shared_ptr(_Right).swap(*this);
        return (*this);
        }

    template<class _Ty2>
        _Myt& operator=(const shared_ptr<_Ty2>& _Right) _NOEXCEPT
        {   // assign shared ownership of resource owned by _Right
        shared_ptr(_Right).swap(*this);
        return (*this);
        }


    void reset() _NOEXCEPT
        {   // release resource and convert to empty shared_ptr object
        shared_ptr().swap(*this);
        }

    template<class _Ux>
        void reset(_Ux *_Px)
        {   // release, take ownership of _Px
        shared_ptr(_Px).swap(*this);
        }

    template<class _Ux,
        class _Dx>
        void reset(_Ux *_Px, _Dx _Dt)
        {   // release, take ownership of _Px, with deleter _Dt
        shared_ptr(_Px, _Dt).swap(*this);
        }

    void swap(_Myt& _Other) _NOEXCEPT
        {   // swap pointers
        this->_Swap(_Other);
        }

    _Ty *get() const _NOEXCEPT
        {   // return pointer to resource
        return (this->_Get());
        }

    typename add_reference<_Ty>::type operator*() const _NOEXCEPT
        {   // return reference to resource
        return (*this->_Get());
        }

    _Ty *operator->() const _NOEXCEPT
        {   // return pointer to resource
        return (this->_Get());
        }

    bool unique() const _NOEXCEPT
        {   // return true if no other shared_ptr object owns this resource
        return (this->use_count() == 1);
        }

    explicit operator bool() const _NOEXCEPT
        {   // test if shared_ptr object owns no resource
        return (this->_Get() != 0);
        }
    };
   
            
template<class _Ty>
    class weak_ptr
        : public _Ptr_base<_Ty>
    {   // class for pointer to reference counted resource
public:
    weak_ptr() _NOEXCEPT
        {   // construct empty weak_ptr object
        }

    weak_ptr(const weak_ptr& _Other) _NOEXCEPT
        {   // construct weak_ptr object for resource pointed to by _Other
        this->_Resetw(_Other);
        }

    template<class _Ty2,
        class = typename enable_if<is_convertible<_Ty2 *, _Ty *>::value,
            void>::type>
        weak_ptr(const shared_ptr<_Ty2>& _Other) _NOEXCEPT
        {   // construct weak_ptr object for resource owned by _Other
        this->_Resetw(_Other);
        }

    template<class _Ty2,
        class = typename enable_if<is_convertible<_Ty2 *, _Ty *>::value,
            void>::type>
        weak_ptr(const weak_ptr<_Ty2>& _Other) _NOEXCEPT
        {   // construct weak_ptr object for resource pointed to by _Other
        this->_Resetw(_Other.lock());
        }

    ~weak_ptr() _NOEXCEPT
        {   // release resource
        this->_Decwref();
        }

    weak_ptr& operator=(const weak_ptr& _Right) _NOEXCEPT
        {   // assign from _Right
        this->_Resetw(_Right);
        return (*this);
        }

    template<class _Ty2>
        weak_ptr& operator=(const weak_ptr<_Ty2>& _Right) _NOEXCEPT
        {   // assign from _Right
        this->_Resetw(_Right.lock());
        return (*this);
        }

    template<class _Ty2>
        weak_ptr& operator=(const shared_ptr<_Ty2>& _Right) _NOEXCEPT
        {   // assign from _Right
        this->_Resetw(_Right);
        return (*this);
        }

    void reset() _NOEXCEPT
        {   // release resource, convert to null weak_ptr object
        this->_Resetw();
        }

    void swap(weak_ptr& _Other) _NOEXCEPT
        {   // swap pointers
        this->_Swap(_Other);
        }

    bool expired() const _NOEXCEPT
        {   // return true if resource no longer exists
        return (this->_Expired());
        }

    shared_ptr<_Ty> lock() const _NOEXCEPT
        {   // convert to shared_ptr
        return (shared_ptr<_Ty>(*this, false));
        }
    };

从这里可以看出来shared_ptr和weak_ptr里面本身并没有成员变量,提供的是对外的接口。shared_ptr可以对外提供模拟内部指针的操作,而weak_ptr是用来提供获取shared_ptr的接口。

具体用来记录保存内部指针和使用次数是他们的共同父类_Ptr_base:

template<class _Ty>
    class _Ptr_base
    {   // base class for shared_ptr and weak_ptr
public:
    typedef _Ptr_base<_Ty> _Myt;
    typedef _Ty element_type;

    _Ptr_base()
        : _Ptr(0), _Rep(0)
        {   // construct
        }

    _Ptr_base(_Myt&& _Right)
        : _Ptr(0), _Rep(0)
        {   // construct _Ptr_base object that takes resource from _Right
        _Assign_rv(_STD forward<_Myt>(_Right));
        }

    template<class _Ty2>
        _Ptr_base(_Ptr_base<_Ty2>&& _Right)
        : _Ptr(_Right._Ptr), _Rep(_Right._Rep)
        {   // construct _Ptr_base object that takes resource from _Right
        _Right._Ptr = 0;
        _Right._Rep = 0;
        }

    _Myt& operator=(_Myt&& _Right)
        {   // construct _Ptr_base object that takes resource from _Right
        _Assign_rv(_STD forward<_Myt>(_Right));
        return (*this);
        }

    void _Assign_rv(_Myt&& _Right)
        {   // assign by moving _Right
        if (this != &_Right)
            _Swap(_Right);
        }

    long use_count() const _NOEXCEPT
        {   // return use count
        return (_Rep ? _Rep->_Use_count() : 0);
        }

    void _Swap(_Ptr_base& _Right)
        {   // swap pointers
        _STD swap(_Rep, _Right._Rep);
        _STD swap(_Ptr, _Right._Ptr);
        }

    template<class _Ty2>
        bool owner_before(const _Ptr_base<_Ty2>& _Right) const
        {   // compare addresses of manager objects
        return (_Rep < _Right._Rep);
        }

    void *_Get_deleter(const _XSTD2 type_info& _Typeid) const
        {   // return pointer to deleter object if its type is _Typeid
        return (_Rep ? _Rep->_Get_deleter(_Typeid) : 0);
        }

    _Ty *_Get() const
        {   // return pointer to resource
        return (_Ptr);
        }

    bool _Expired() const
        {   // test if expired
        return (!_Rep || _Rep->_Expired());
        }

    void _Decref()
        {   // decrement reference count
        if (_Rep != 0)
            _Rep->_Decref();
        }

    void _Reset()
        {   // release resource
        _Reset(0, 0);
        }

    template<class _Ty2>
        void _Reset(const _Ptr_base<_Ty2>& _Other)
        {   // release resource and take ownership of _Other._Ptr
        _Reset(_Other._Ptr, _Other._Rep);
        }

    template<class _Ty2>
        void _Reset(const _Ptr_base<_Ty2>& _Other, bool _Throw)
        {   // release resource and take ownership from weak_ptr _Other._Ptr
        _Reset(_Other._Ptr, _Other._Rep, _Throw);
        }

    template<class _Ty2>
        void _Reset(const _Ptr_base<_Ty2>& _Other, const _Static_tag&)
        {   // release resource and take ownership of _Other._Ptr
        _Reset(static_cast<_Ty *>(_Other._Ptr), _Other._Rep);
        }

    template<class _Ty2>
        void _Reset(const _Ptr_base<_Ty2>& _Other, const _Const_tag&)
        {   // release resource and take ownership of _Other._Ptr
        _Reset(const_cast<_Ty *>(_Other._Ptr), _Other._Rep);
        }

    template<class _Ty2>
        void _Reset(const _Ptr_base<_Ty2>& _Other, const _Dynamic_tag&)
        {   // release resource and take ownership of _Other._Ptr
        _Ty *_Ptr = dynamic_cast<_Ty *>(_Other._Ptr);
        if (_Ptr)
            _Reset(_Ptr, _Other._Rep);
        else
            _Reset();
        }

    template<class _Ty2>
        void _Reset(auto_ptr<_Ty2>&& _Other)
        {   // release resource and take _Other.get()
        _Ty2 *_Px = _Other.get();
        _Reset0(_Px, new _Ref_count<_Ty>(_Px));
        _Other.release();
        _Enable_shared(_Px, _Rep);
        }

    template<class _Ty2>
        void _Reset(_Ty *_Ptr, const _Ptr_base<_Ty2>& _Other)
        {   // release resource and alias _Ptr with _Other_rep
        _Reset(_Ptr, _Other._Rep);
        }

    void _Reset(_Ty *_Other_ptr, _Ref_count_base *_Other_rep)
        {   // release resource and take _Other_ptr through _Other_rep
        if (_Other_rep)
            _Other_rep->_Incref();
        _Reset0(_Other_ptr, _Other_rep);
        }

    void _Reset(_Ty *_Other_ptr, _Ref_count_base *_Other_rep, bool _Throw)
        {   // take _Other_ptr through _Other_rep from weak_ptr if not expired
            // otherwise, leave in default state if !_Throw,
            // otherwise throw exception
        if (_Other_rep && _Other_rep->_Incref_nz())
            _Reset0(_Other_ptr, _Other_rep);
        else if (_Throw)
            _THROW_NCEE(bad_weak_ptr, 0);
        }

    void _Reset0(_Ty *_Other_ptr, _Ref_count_base *_Other_rep)
        {   // release resource and take new resource
        if (_Rep != 0)
            _Rep->_Decref();
        _Rep = _Other_rep;
        _Ptr = _Other_ptr;
        }

    void _Decwref()
        {   // decrement weak reference count
        if (_Rep != 0)
            _Rep->_Decwref();
        }

    void _Resetw()
        {   // release weak reference to resource
        _Resetw((_Ty *)0, 0);
        }

    template<class _Ty2>
        void _Resetw(const _Ptr_base<_Ty2>& _Other)
        {   // release weak reference to resource and take _Other._Ptr
        _Resetw(_Other._Ptr, _Other._Rep);
        }

    template<class _Ty2>
        void _Resetw(const _Ty2 *_Other_ptr, _Ref_count_base *_Other_rep)
        {   // point to _Other_ptr through _Other_rep
        _Resetw(const_cast<_Ty2*>(_Other_ptr), _Other_rep);
        }

    template<class _Ty2>
        void _Resetw(_Ty2 *_Other_ptr, _Ref_count_base *_Other_rep)
        {   // point to _Other_ptr through _Other_rep
        if (_Other_rep)
            _Other_rep->_Incwref();
        if (_Rep != 0)
            _Rep->_Decwref();
        _Rep = _Other_rep;
        _Ptr = _Other_ptr;
        }

private:
    _Ty *_Ptr;
    _Ref_count_base *_Rep;
    template<class _Ty0>
        friend class _Ptr_base;
    };

可以看到这个类里面主要提供了两个成员

  • 成员_Ty *_Ptr主要用来记录内部指针。

  • 成员_Ref_count_base *_Rep用来记录使用次数和弱指针使用次数。

​ 实际上_Ref_count_base *_Rep 这个指针也是new出来的,当weak_count为0时就可以删除,而使用次数是用来记录内部指针的,当使用次数为0时,就可以释放内部指针了。

一些重要的成员函数:

Reset _Decref _Decwref use_count等。

再来看看类_Ref_count_base的实现:

class _Ref_count_base
    {   // common code for reference counting
private:
    virtual void _Destroy() = 0;
    virtual void _Delete_this() = 0;

private:
    _Atomic_counter_t _Uses;
    _Atomic_counter_t _Weaks;

protected:
    _Ref_count_base()
        {   // construct
        _Init_atomic_counter(_Uses, 1);
        _Init_atomic_counter(_Weaks, 1);
        }

public:
    virtual ~_Ref_count_base() _NOEXCEPT
        {   // ensure that derived classes can be destroyed properly
        }

    bool _Incref_nz()
        {   // increment use count if not zero, return true if successful
        for (; ; )
            {   // loop until state is known
 #if defined(_M_IX86) || defined(_M_X64) || defined(_M_CEE_PURE)
            _Atomic_integral_t _Count =
                static_cast<volatile _Atomic_counter_t&>(_Uses);

            if (_Count == 0)
                return (false);

            if (static_cast<_Atomic_integral_t>(_InterlockedCompareExchange(
                    reinterpret_cast<volatile long *>(&_Uses),
                    _Count + 1, _Count)) == _Count)
                return (true);

 #else /* defined(_M_IX86) || defined(_M_X64) || defined(_M_CEE_PURE) */
            _Atomic_integral_t _Count =
                _Load_atomic_counter(_Uses);

            if (_Count == 0)
                return (false);

            if (_Compare_increment_atomic_counter(_Uses, _Count))
                return (true);
 #endif /* defined(_M_IX86) || defined(_M_X64) || defined(_M_CEE_PURE) */
            }
        }

    unsigned int _Get_uses() const
        {   // return use count
        return (_Get_atomic_count(_Uses));
        }

    void _Incref()
        {   // increment use count
        _MT_INCR(_Mtx, _Uses);
        }

    void _Incwref()
        {   // increment weak reference count
        _MT_INCR(_Mtx, _Weaks);
        }

    void _Decref()
        {   // decrement use count
        if (_MT_DECR(_Mtx, _Uses) == 0)
            {   // destroy managed resource, decrement weak reference count
            _Destroy();
            _Decwref();
            }
        }

    void _Decwref()
        {   // decrement weak reference count
        if (_MT_DECR(_Mtx, _Weaks) == 0)
            _Delete_this();
        }

    long _Use_count() const
        {   // return use count
        return (_Get_uses());
        }

    bool _Expired() const
        {   // return true if _Uses == 0
        return (_Get_uses() == 0);
        }

    virtual void *_Get_deleter(const _XSTD2 type_info&) const
        {   // return address of deleter object
        return (0);
        }
    };

在这个_Ref_count_base类中提供了

_Atomic_counter_t _Uses;

​ _Atomic_counter_t _Weaks;

实际上就是记录的内部指针使用次数和_Ref_count_base使用次数。

在这里有一个简单的继承关系:_Ref_count继承自_Ref_count_base

template<class _Ty>
    class _Ref_count
    : public _Ref_count_base
    {   // handle reference counting for object without deleter
public:
    _Ref_count(_Ty *_Px)
        : _Ref_count_base(), _Ptr(_Px)
        {   // construct
        }

private:
    virtual void _Destroy()
        {   // destroy managed resource
        delete _Ptr;
        }

    virtual void _Delete_this()
        {   // destroy self
        delete this;
        }

    _Ty * _Ptr;
    };

这里_Ptr_base中的_Ref_count_base* _Rep成员是使用的new _Ref_count。

void _Resetp(_Ux *_Px)
{   // release, take ownership of _Px
    _TRY_BEGIN  // allocate control block and reset
        _Resetp0(_Px, new _Ref_count<_Ux>(_Px));
    _CATCH_ALL  // allocation failed, delete resource
        delete _Px;
    _RERAISE;
    _CATCH_END
}

然后使用子类转父类,而这个类实现了_Ref_count_base*的两个接口函数:

virtual void _Destroy() = 0;

virtual void _Delete_this() = 0;

这个引用的次数:

    _Ref_count_base()
        {   // construct
        _Init_atomic_counter(_Uses, 1);
        _Init_atomic_counter(_Weaks, 1);
        }

接下来分析内存结构:

这里有两个成员,分别是

_Ptr(内部指针) :0x71b8d0

_Rep(引用base):0x0071b910

而引用base这里实际上是_Ref_count对象,因为有虚函数,所以这里存在虚表指针:

前4个字节是虚表指针

中间两个4字节分别是内部对象计数器和自身的计数器。

最后4个字节是内部对象指针。

到这里就shared_ptr与weak_ptr的代码就分析的差不多了

最后说一下计数器增减的规则:

初始化及增加的情形:

  • 当创建一个新的shared_ptr时,内部对象计数器和自身的计数器均置1.

  • 当将另外一个shared_ptr赋值给新的shared_ptr时,内部对象计数器+1,自身计数器不变。

  • 当将另外一个shared_ptr赋值给新的weak_ptr时,内部对象计数器不变,自身计数器+1。

  • 当从weak_ptr获取一个shared_ptr时,内部对象计数器+1,自身计数器不变。

减少的情形:

  • 当一个shared_ptr析构时,内部对象计数器-1。当内部对象计数器减为0时,则释放内部对象,并将自身计数器-1。

  • 当一个weak_ptr析构时,自身计数器-1。当自身计数器减为0时,则释放自身_Ref_count*对象。

那么就可以自己来模拟强弱指针,并修改成模板。

#include "stdafx.h"
#include <memory>


/*
    问题1:
     为什么会存在强弱指针的计数?
     A{

        B对象弱智能指针(引用次数  1) weak_ptr_uses_count
     }

     B{
     
        A对象智能指针(引用次数  2)   shared_ptr_uses_count
     }




    问题2:
     强弱指针计数的用途是什么,具体的代码实现是什么?

     shared_ptr :  对外提供接口,并无成员变量 表示强指针
               父类:_Ptr_base

     weak_ptr   :  对外提供接口,并无成员变量 表示弱指针
               父类:_Ptr_base

     _Ptr_base{
 
        两个成员变量:
            _Ty *_Ptr;    //表示智能指针关联的原始的指针, 内部指针
            _Ref_count_base *_Rep; //用于管理智能指针的次数
     }

     基类 纯虚类
     _Ref_count_base{
            virtual void _Destroy() _NOEXCEPT = 0;
            virtual void _Delete_this() _NOEXCEPT = 0;

            //实际上表达的是当前有多少个强指针在引用内部指针
            _Atomic_counter_t _Uses;  //表示强指针使用次数 

            //实际上表达的是当前_Ref_count_base类型的使用次数
            _Atomic_counter_t _Weaks; //表示弱指针使用次数
     }

     有一个派生类:
     _Ref_count: //真正的计数器对象,使用时,需要将指针强转为父类指针,仅仅使用接口
                _Ref_count_base
     {
        //派生类多了一个成员
        _Ty * _Ptr; //表达的是内部指针
     }

     //强指针构造,析构,=赋值 拷贝构造等情况下,计数器的变化
     //弱指针构造,析构,=赋值 拷贝构造等情况下,计数器的变化
     //弱指针提升为强指针时,计数器的变化

     //强指针直接构造(拿原始指针构造)时:
     //1. 初始化_Ty * _Ptr
     //2. 创建_Ref_count对象
     //3. _Ref_count_base对象构造时,会分别为_Uses = 1 并且 _Weaks = 1

*/

int _tmain(int argc, _TCHAR* argv[])
{
    std::shared_ptr<int> sptr(new int(3));
    std::shared_ptr<int> sptr2 = sptr;

    std::weak_ptr<int> wptr = sptr;

    if (!wptr.expired()) {
        std::shared_ptr<int> sptr3 = wptr.lock();
    }

    return 0;
}

再次编写智能指针

经过前面的分析,我们彻底理解了智能指针的使用,因此,这里我们根据stl中的智能指针的写法,对自己版本的智能指针进行修改,从而到底彻底理解智能指针的目的。

#pragma once

class CRefCount
{
public:
    CRefCount(){
        m_nUsedCount = 1;
        m_nWeakCount = 1; 
    }

    void incUsed(){
        m_nUsedCount++;
    }

    int decUsed(){
        m_nUsedCount--;
        return m_nUsedCount;
    }

    void incWeak(){
        m_nWeakCount++;
    }

    int decWeak(){
        m_nWeakCount--;

        return m_nWeakCount;
    }

    int getUsed(){
        return m_nUsedCount;
    }

private:
    int m_nUsedCount; //强指针引用次数
    int m_nWeakCount; //弱指针引用次数
};

template<typename T>
class CMySmartPtrBase
{
public:
    CMySmartPtrBase(){};
    ~CMySmartPtrBase(){};

    void destroy(){
        delete m_Ptr;
    }

    void release(){
        if (m_pRef != nullptr && m_pRef->decWeak() == 0){
            delete m_pRef;
        }
    }

protected:
    T* m_Ptr;
    CRefCount* m_pRef;
};

//强指针类型
template<typename T>
class CMyWeakPtr;

template<typename T>
class CStrongPtr : public CMySmartPtrBase<T>
{
    friend class CMyWeakPtr<T>;
public:
    CStrongPtr(){
        m_Ptr = nullptr;
        m_pRef = nullptr;
    }

    explicit CStrongPtr(T* p){
        m_Ptr = p;
        m_pRef = new CRefCount;
    }

    CStrongPtr(CStrongPtr<T>& obj){

        m_Ptr = obj.m_Ptr;
        obj.m_pRef->incUsed();
        m_pRef = obj.m_pRef;
    }

    CStrongPtr<T>& operator=(CStrongPtr<T>& obj){

        if (m_pRef != nullptr && m_pRef->decUsed() == 0){
            destroy();
            release();
        }

        m_Ptr = obj.m_Ptr;
        obj.m_pRef->incUsed();
        m_pRef = obj.m_pRef;

        return *this;
    }

    CStrongPtr(CMyWeakPtr<T>& obj){
        m_Ptr = obj.m_Ptr;
        obj.m_pRef->incUsed();
        m_pRef = obj.m_pRef;
    }

    ~CStrongPtr(){
        if (m_pRef != nullptr && m_pRef->decUsed() == 0){
            destroy();
            release();
        }
    }

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

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

    T* get(){
        return m_Ptr;
    }
};


//强指针类型
template<typename T>
class CMyWeakPtr : public CMySmartPtrBase<T>
{
public:
    friend class CStrongPtr<T>;

    CMyWeakPtr(){
        m_Ptr = nullptr;
        m_pRef = nullptr;
    }

    CMyWeakPtr(CStrongPtr<T>& obj){
        //release();

        m_Ptr = obj.m_Ptr;
        obj.m_pRef->incWeak();
        m_pRef = obj.m_pRef;
    }

    CMyWeakPtr<T>& operator = (CStrongPtr<T>& obj){
        release();

        m_Ptr = obj.m_Ptr;
        obj.m_pRef->incWeak();
        m_pRef = obj.m_pRef;

        return *this;
    }

    CMyWeakPtr(CMyWeakPtr<T>& obj){

        m_Ptr = obj.m_Ptr;
        obj.m_pRef->incWeak();
        m_pRef = obj.m_pRef;
    }

    ~CMyWeakPtr(){
        release();
    }

    CStrongPtr<T>& lock(){
        if (m_pRef == nullptr){
            return CStrongPtr<T>();
        }

        return CStrongPtr<T>(*this);
    }

    bool IsExpried(){
        if (m_pRef == nullptr){
            return true;
        }

        return m_pRef->getUsed() == 0;
    }
};

main:

// MySharedPtr.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "MySmartPtr.h"

class CSon;

class CTest{
public:

    void set(CStrongPtr<CSon> p2){
        m_p1 = p2;
    }

    CStrongPtr<CSon> m_p1;
};

class CSon{
public:

    void set(CStrongPtr<CTest> p2){
        m_p1 = p2;
    }

    CMyWeakPtr<CTest> m_p1;
};

void foo(){
    CTest* father = new CTest();
    CSon* son = new CSon();


    CStrongPtr<CTest> ptrFather(father);
    CStrongPtr<CSon> ptrSon(son);

    father->set(ptrSon);
    son->set(ptrFather);

}

int _tmain(int argc, _TCHAR* argv[])
{
    foo();

    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_37581730/article/details/114458604