C++智能指针

RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源的技术

  • 不需要显示的释放资源
  • 对象的资源在其生命周期类保持有效

通常控制的资源:动态申请的内存、文件描述符、互斥量、网络连接等

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象 。

智能指针原理

  • RAII特性

  • 具有像指针一样的行为

    (重载operator*()operator->()

template<class T>
class SmartPtr {
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}

    //RAII特性
	~SmartPtr()
	{
		if (_ptr)
		{
			delete _ptr;
		}
	}
    
	//具有像指针一样的行为
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};

指针允许拷贝和赋值,使多个指针对象指向同一块资源,即浅拷贝。如果使用上述代码的SmartPtr,并且进行过拷贝赋值,那么很可能会导致野指针问题。这样的话对于SmartPtr的重点就在解决:

  • 智能指针对象的拷贝问题

auto_ptr

C++98中引入的智能指针

通过管理权转移的方式解决智能指针的拷贝问题,保证一个资源在任何时刻都只有一个对象在对其进行管理,这时同一个资源就不会被多次释放了

  • 拷贝构造函数中,用传入对象来初始化当前对象,并将传入对象管理资源的指针置空
  • 赋值运算符重载中,先将当前管理的资源释放,然后再接管传入对象管理的资源,并将传入对象管理资源的指针置空
template<class T>
class auto_ptr
{
public:
    auto_ptr(T* ptr = nullptr)
        : _ptr(ptr)
    {} 
    ~auto_ptr()
    {
        if (_ptr)
        {
            cout << "Delete:" << _ptr << endl;
            delete _ptr;
        }
    }
    T& operator*() { return *_ptr; }
    T* operator->() { return _ptr; }
    
    //智能指针对象的拷贝问题
    
    // 拷贝构造函数
    auto_ptr(auto_ptr<T>& ap)
        :_ptr(ap._ptr)
    {
        ap._ptr = nullptr;
    }

    // 赋值运算重载
    auto_ptr<T>& operator=(auto_ptr<T>& ap)
    {
        if (this != &ap)//防止自己给自己赋值的操作
        {
            if (_ptr)//释放原来管理的资源
            {
                delete _ptr;
            }
			//管理权转移
            _ptr = ap._ptr;
            ap._ptr = nullptr;
        }

        return *this;
    }
private:
    T* _ptr;
};

将一个auto_ptr对象赋值给另一个对象,原本的auto_ptr对象将拥有NULL指针,此时出现“悬垂指针”问题,比如:继续使用原对象,导致空指针解引用等问题。

由于这个缺陷,导致auto_ptr成为一种过时的智能指针,而后C++11中有更加安全可靠的选择。

unique_ptr

C++11中引入的智能指针

通过简单粗暴的防止对智能指针对象进行拷贝,这样也能保证资源不会被多次释放

  • 拷贝构造函数和赋值运算符被显式删除
template<class T>
class unique_ptr
{
// 防拷贝 C++98 只声明不实现 
//private:
	//unique_ptr(unique_ptr<T>& ap);
	//unique_ptr<T>& operator=(unique_ptr<T>& ap);
public:
	unique_ptr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~unique_ptr()
	{
		if (_ptr)
		{
			cout << "Delete:" << _ptr << endl;
			delete _ptr;
		}
	}
	T& operator*() { return *_ptr; }
	T* operator->() { return _ptr; }
    
	// 防拷贝 C++11
	unique_ptr(unique_ptr<T>& ap) = delete;
	unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;
private:
	T* _ptr;
};

shared_ptr

C++11引入的更安全可靠的智能指针

通过引用计数的方式来实现多个shared_ptr对象之间共享资源

  • 每个被管理的资源都有一个引用计数,初始化为1
  • 当新对象管理该资源时,引用计数+1,当某对象不再管理该资源时,引用计数-1
  • 当该引用计数为0时,会将该资源释放
template<class T>
class shared_ptr
{
public:
	shared_ptr(T* ptr = nullptr)
		:_ptr(ptr)
		, _pcount(new int(1))
	{}

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

	T* operator->() { return _ptr; }
    
	//析构后不再管理该资源
	~shared_ptr()
	{
		if (--(*_pcount) == 0)
		{
			if (_ptr != nullptr)
			{
				delete _ptr;
				_ptr = nullptr;
			}
			delete _pcount;
			_pcount = nullptr;
		}
	}
    //拷贝构造函数
	shared_ptr(shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		, _pcount(sp._pcount)
	{
		(*_pcount)++;
	}
    //赋值,不再管理原资源
	shared_ptr& operator=(shared_ptr<T>& sp)
	{
		if (_ptr != sp._ptr) //避免管理同资源的对象间赋值
		{
			if (--(*_pcount) == 0) //引用计数--
			{
				delete _ptr;
				delete _pcount;
			}
            //管理同一块资源
			_ptr = sp._ptr;       
			_pcount = sp._pcount; 
			(*_pcount)++;         //引用计数++
		}
		return *this;
	}

private:
	T* _ptr;      //管理的资源
	int* _pcount; //管理的资源对应的引用计数
};

引用计数使用动态申请的空间

引用计数本身也是智能指针所管理/共享的资源,使用指针类型可以进行管理/共享。

如果使用int型变量,管理同一个资源时无法很好保证引用计数的一致性;如果使用static变量,同类的实例化对象其实是共享该静态成员的,即使管理的不是同一个资源。

引用计数++/- -操作的线程安全问题

可以使用mutex* _pmutex互斥锁来对引用计数进行加锁操作,同样的互斥锁也使用指针类型。

定制删除器

share_ptr并不是只管理以new方式申请到的内存空间,也可能是以new[]的方式申请到的空间,或管理的是一个文件指针。此时就需要使用特定的删除器

template<class T>
struct Delete
{
    void operator()(T* ptr)
    {
        //1. delete[] ptr
        //2. close(ptr)
        delete ptr;
    }
};

template<class T, class D = Delete<T>>
class shared_ptr
{
    void Release()
    {
        if (--(*_pCount) == 0)
        {
            D()(_ptr);	// 定制删除器
            delete _pCount;
        }
    }
    ~shared_ptr() { Release(); }
    。。。
};

循环引用问题

虽然在赋值运算符重载里面用_ptr != sp._ptr进行判断,避免管理同资源的对象间赋值,导致的重复计数问题。但是还是存在特殊的场景,诱发引用计数的错误----循环引用

struct ListNode
{
	shared_ptr<ListNode> _next;
	share_ptr<ListNode> _prev;
};
int main()
{
	shared_ptr<ListNode> node1(new ListNode);
	shared_ptr<ListNode> node2(new ListNode);
	//同类型(share_ptr)才能进行赋值拷贝
	node1->_next = node2;
	node2->_prev = node1;

	return 0;
}

在这里插入图片描述

只有当引用计数为0时资源才会释放。第一个节点引用计数变为0取决于第二个节点的prev指针,因此需要释放第二个节点,即:第二个节点的引用计数变0,这一步又取决于第一个节点的next指针…

因此第一个节点是释放取决于第二个节点,而第二个节点又需要第一个节点先释放。

weak_ptr

C++11引入的智能指针,可以与shared_ptr协同工作

在不增加对象引用计数的情况下观测和访问共享对象,不进行资源管理

  • 支持使用shared_ptr对象来进行构造/赋值
// 辅助型智能指针,使命配合解决shared_ptr循环引用问题
template<class T>
class weak_ptr
{
public:
	weak_ptr()
		:_ptr(nullptr)
	{}
	T& operator*() { return *_ptr; }
	T* operator->() { return _ptr; }
    
    //用share_ptr构建对象
	weak_ptr(const shared_ptr<T>& sp)
		:_ptr(sp.get())
	{}

	weak_ptr(const weak_ptr<T>& wp)
		:_ptr(wp._ptr)
	{}
    //用share_ptr对象进行赋值拷贝
	weak_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
		_ptr = sp.get();
		return *this;
	}
public:
	T* _ptr;
};

对于shared_ptr问题进行修改

struct ListNode
{
	weak_ptr<ListNode> _next;
	weak_ptr<ListNode> _prev;
};
int main()
{
	shared_ptr<ListNode> node1(new ListNode);
	shared_ptr<ListNode> node2(new ListNode);
	//weak_ptr支持shared_ptr类型的赋值拷贝
	node1->_next = node2;
	node2->_prev = node1;

	return 0;
}

希望暂时访问一个shared_ptr所指向的对象而不想把它引用计数加1时,可以使用weak_ptr代替 shared_ptr。


    🦀🦀观看~~

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值