智能指针的实现

概念

  智能指针本质是对象,但行为却和指针极其相似。
  是存储指向动态分配(堆)对象指针的类,把一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。

为什么需要智能指针?
  比如在异常学习中我们知道,异常很可能会造成资源泄漏,而且由于C++没有垃圾回收机制,就会导致那些申请了的忘记释放的资源的浪费,所以产生智能指针来解决这些问题。

智能指针的使用及原理:

  RAII:

   RAII是一种利用对象生命周期来控制程序资源的技术。
   在对象构造时将资源交给该对象,保证资源在对象生命周期内有效,在对象析构时释放资源。

   优点:
    - 不需要显示释放资源
    - 对象所需资源在其生命周期内始终有效

   // RAII具体实现实例
template<class T>
class SmartPtr{
public:
    // 调用构造函数,所创建对象获取资源
    SmartPtr(T* ptr = nullptr)
        :_ptr(ptr)
    {}    
    
    // 析构时就将资源释放
    ~SmartPtr()
    {
      if(_ptr)
        delete _ptr;
    }
    
private:
    T* _ptr;    
};

void func()
{
    int* temp = new int [size];
    // 为防止内存泄漏,将temp的这块资源托管给 对象sp
    SmartPtr<int> sp(temp);
    
    ... // 其他代码段
}

int main()
{
    try{
        func();
    }
    catch(const exception& e){
        cout << e.what() << endl;
    }
}

除了RAII技术,还需要将 *和 ->重载,使得我们的sp对象能具有指针一样的操作。

// 重载方式比较简单
T& operator*() {return *_ptr;}
T& operator->() {return _ptr;}

所以智能指针原理: RAII + (* ->)运算符重载

如果我们要将我们的资源拷贝一份,通过执行下面代码:

#include <iostream>

using namespace std;

template<class T>
class SmartPtr{
public:
	// 调用构造函数,所创建对象获取资源
	SmartPtr(T* ptr = nullptr)
		:_ptr(ptr)
	{}

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

	// 析构时就将资源释放
	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
		cout << "~SmartPtr Success" << endl;
	}

private:
	T* _ptr;
};

void func()
{
	int* temp = new int [100];
	// 为防止内存泄漏,将temp的这块资源托管给 对象sp
	SmartPtr<int> sp(temp);
	SmartPtr<int> sp2(sp);

}

int main()
{
	try{
		func();
	}
	catch (const exception& e){
		cout << e.what() << endl;
	}

	return 0;
}

在这里插入图片描述
通过运行结果可以发现现在所实现的智能指针存在浅拷贝的问题。
  浅拷贝:也叫位拷贝,就是成员数据间的赋值。但如果对象中包含资源时,就产生两个对象指向同一资源,且都能对资源进行访问、控制。

那要怎么解决?
 浅拷贝解决方式通常为:
 1. 深拷贝:也叫值拷贝,它不仅将对象的数据拷贝一份,也会将对象的资源拷贝一份。
   但这里不能使用深拷贝,因为这里的资源是用户申请的。
 2. 写时拷贝:维护一个计数(指针变量),记录使用资源的对象,只有到最后一个对象时,才由该对象完成资源的释放

C++98中的智能指针:

auto_ptr:

  头文件 #include
  std::auto_ptr 能够方便的管理单个堆内存对象。
  原理:将资源的管理权转移的思想,即每创建一个对象,新对象指向资源,旧对象与资源断开。

// 重载拷贝构造
	SmartPtr(SmartPtr<T>& copy)
		:_ptr(copy._ptr)
	{
		copy._ptr = nullptr;
	}

	SmartPtr<T>& operator=(SmartPtr<T>& copy)
	{
		// 检测是否自己给自己赋值
		if (this != &copy){
			// 释放当前对象资源
			if (_ptr)
				delete _ptr;
			
			// 资源管理权传递 
			_ptr = copy._ptr;
			copy._ptr = nullptr;
		}

	}

之前浅拷贝问题就得到解决 
在这里插入图片描述
但是在代码实现中我们知道,这个过程中断开了了旧对象与资源的联系,那就表示我们不能再通过之前的对象访问资源。
所以auto_ptr是存在很大的漏洞,到C++11 也已经被淘汰了。

C++11下的智能指针:

unique_ptr:

  头文件 #include。
  原理:禁止拷贝和赋值,对象独占一份资源。
  使用:
   unique_ptr虽然不能赋值,但是可以通过move()函数转移对象的所有权(有点类似于auto_ptrde caoz );
   reset()可以提前释放unique_ptr指针;

代码实现:

template<class T>
class SmartPtr{
public:
	// 调用构造函数,所创建对象获取资源
	SmartPtr(T* ptr = nullptr)
		:_ptr(ptr)
	{}

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

	// 析构时就将资源释放
	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
		cout << "~SmartPtr Success" << endl;
	}

private:
	T* _ptr;

	// C++11 中防止拷贝的方式:delete
	SmartPtr (SmartPtr<T> const &) = delete;

	SmartPtr& operator=(SmartPtr<T>& ) = delete;

};
shared_ptr:

  原理:通过引用计数的方式来实现多个shared_ptr对象间的资源共享。
  使用:

  • 智能指针是具有指针行为的类,所以初始化时不能直接将指针赋值给智能指针。可以使用构造、拷贝构造和make_shared函数初始化;
  • 在多线程时,就需要对属于临界资源的引用计数进行保护。可以加锁保证线程安全;
  • 智能指针的对象存放在堆区,如果有两个线程同时访问,就也会导致线程安全问题;
  • 不要用原始指针初始化多个shared_ptr,否则会造成二次释放同一资源;
  • 注意避免循环引用。

代码实现:

include <iostream>
#include <thread>
#include <mutex>///

using namespace std;

template<class T>
class SharedPtr{
public:
	SharedPtr(T* ptr = nullptr)
		: _ptr(ptr)
		, _pCount(new int(1))
		, _pMutex(new mutex)
	{
		// 如果是一个空指针对象,引用计数给0
		if (_ptr == nullptr)
			*_pCount = 0;
	}

	SharedPtr(SharedPtr<T>& copy)
		: _ptr(copy._ptr)
		, _pCount(copy._pCount)
		, _pMutex(copy._pMutex)
	{
		if (_ptr)
			AddCount();
	}

	SharedPtr<T>& operator=(const SharedPtr<T>& copy)
	{
		// 防止自赋值
		if (_ptr != copy_ptr){
			// 释放_ptr旧资源
			Release();

			// 共享copy对象资源
			_ptr = copy._ptr;
			// 计数增加
			_pCount = copy._pCount;
			_pMutex = copy._pMutex;

			if (_ptr)
				AddCount();
		}
		return *this;
	}

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

	// 查看当前计数
	int UseCount() { return *_pCount; }
	
	// 获取原始指针
	T* Get(){ return _ptr; }

	// 如果有新对象,增加引用计数
	int AddCount()
	{
		// 为保证多线程下的线程安全,执行锁操作
		_pMutex->lock();

		++(*_pCount);
		_pMutex->unlock();
		return *_pCount;
	}

	// 如果有对象调用析构,减少引用计数
	int SubCount()
	{
		_pMutex->lock();

		--(*_pCount);
		_pMutex->unlock();
		return *_pCount;
	}

	~SharedPtr()
	{
		Release();
	}

private:
	// 释放资源
	void Release()
	{
		// 如果引用计数减为0,则释放资源
		if (_ptr && SubCount() == 0){
			delete _ptr;
			delete _pCount;
		}

	}

private:
	int* _pCount;  // 引用计数
	T* _ptr;  // 指向管理资源的指针
	mutex* _pMutex;  // 互斥锁
};

int main()
{
	SharedPtr<int> sp1(new int(10));
	cout << "Before Add SP2:" << sp1.UseCount() << endl;
	SharedPtr<int> sp2(sp1);
	cout << "After Add SP2:" << sp1.UseCount() << endl;
	
	return 0;
}

资源共享
在这里插入图片描述
引用计数
在这里插入图片描述

除了线程安全问题外,还可能产生其他问题:
在这里插入图片描述
解决这个死锁问题可以通过:1. atomic int_32t 2. 范围锁(scoped_t lock)

另外就是shared_ptr的循环引用

测试代码:

class ListNode{
public:
	int _data;
	shared_ptr<ListNode> _prev;
	shared_ptr<ListNode> _next;

	~ListNode() { cout << "~ListNode()" << endl; }
};

int main()
{
	shared_ptr<ListNode> node1(new ListNode);
	shared_ptr<ListNode> node2(new ListNode);

	cout << node1.use_count() << endl;  // 1
	cout << node2.use_count() << endl;  // 1

	node1->_next = node2;
	node2->_prev = node1;

	cout << node1.use_count() << endl;	// 2
	cout << node2.use_count() << endl;	// 2

	return 0;

}

运行结果:
在这里插入图片描述
可以发现程序结束但都没有调用析构函数释放资源,为什么呢?

循环引用:
  -如果node1要析构,就要node2的_prev先析构;
  -而_prev是node2的成员,就要node2析构;
  -但node2被node1的_next管理,就要求node1的_next先析构;
  -而node1的_next要析构,又要求node1先析构。
  -如此循环引用,析构函数一直调不到。

怎么解决循环引用的问题呢?
  在C++11中有weak_ptr专门来帮助解决循环引用的问题。
  只要使用weak_ptr去声明_prev和_next就可以解决。

weak_ptr<ListNode> _prev;
weak_ptr<ListNode> _next;

运行结果:
在这里插入图片描述
weak_ptr:
  它没有重载的operator* 和 -> ,所以不具有普通指针的功能。
  作用:相当于shared_ptr的助理,仅仅为了解决循环引用。
  原理:
   -weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,但weak_ptr没有共享资源,所以它的构造不会引起指针
   -引用计数的增加;
   -weak_ptr通过成员函数lock()从被观测的shared_ptr中获得一个可用的shared_ptr对象,从而操作资源;
当被观察的shared_ptr失效后,相应的weak_ptr也相应失效。

 C++11和boost中智能指针的关系:

C++11的智能指针大都参考boost中实现的。

stdboost功能说明
unique_ptrscoped_ptr独占指针对象,并保证指针所指对象生命周期与其一致
shared_ptrshared_ptr可共享指针对象,可以赋值给shared_ptr或weak_ptr。指针所指对象在所有的相关联的shared_ptr生命周期结束时结束,是强引用。
weak_ptrweak_ptr它不能决定所指对象的生命周期,引用所指对象时,需要lock()成shared_ptr才能使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值