C++第十二天:智能指针(shared_ptr, unique_ptr,auto_ptr 明理和模拟实现)

1.智能指针的使用及其原理

1.1 RAII

  • RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源的简单技术
  • 在对象构造的时候获取资源,接着控制对资源的访问使之在对象的生命周期期间内始终保持有效,最后在对象析构的时候释放资源。这么做有两个好处
    1.不需要显示地释放内存
    2.采用这种方式,对象所需的资源在生命周期内始终保持有效。
  • 用昨天抛异常导致无法释放空间的地方举例:
template<class T>
class Smartptr
{
public:
	Smartptr(T* ptr = nullptr)
		:_ptr(ptr)
	{
	}

	~Smartptr()
	{
		if (_ptr)
		{
			delete _ptr;
			cout << "delete[] "<< endl;
		}
	}

private:
	T* _ptr;
};

double Division(int a, int b)
{
	if (b == 0)
		throw "Division by zero condition!";
	else
		return ((double)a / (double)b);
}

void Func()
{
	int* array = new int[10];
	memset(array, 0, sizeof(int)* 10);
	Smartptr<int> s(array);

	int len, time;
	cin >> len >> time;
	cout << Division(len, time) << endl;	
}

int main()
{
	try
	{
		Func();
	}

	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}

	system("pause");
}
  • 结果
    在这里插入图片描述
    由图可知在抛出了异常的同时释放了空间。

1.2智能指针的作用

  • 上面写的Smartptr还不能被称为智能指针,因为它还没有具备指针的行为。指针可以解引用,也可以通过->访问,空间中的内容。因此Smartptr还需重载*,->重载,才能像指针一样使用。
template<class T>
class Smartptr
{
public:
   Smartptr(T* ptr = nullptr)
   	:_ptr(ptr)
   {
   }

   ~Smartptr()
   {
   	if (_ptr)
   	{
   		delete _ptr;
   	}
   }

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

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

struct Date
{
   int year;
   int month;
   int day;
};

int main()
{
   Smartptr<int> p(new int);
   *p = 10;
   cout << *p << endl;
   
   //需要注意的是这里应该是p1.operator()->()->year = 2019
   //本来应该是p1->->year这里语法上为了可读性,省略了一个->
   Smartptr<Date> p1( new Date);
   p1 -> year = 2019;
   p1 -> month = 3;
   p1 -> day = 31;
   system("pause");
}

总结:

智能指针的原理:
1.RAII特性
2.重载operator*和operator->,具有指针一样的行为。

1.3 std :: auto_ptr(C++98)

  • C++98的库中就提供auto_ptr的智能指针。下面是代码实现
//C++的智能指针都存在<memory>这个库里边
#include<memory>
class Date
{
public:
	Date()
	{
		cout << "Date()" << endl;
	}
	~Date()
	{
		cout << "~Date" << endl;
	}
	int _year;
	int _month;
	int _day;
};

int main()
{
	auto_ptr<Date> ap(new Date);
	auto_ptr<Date> copy(ap);
	ap->_year = 2019;
	system("pause");
}

运行结果:
在这里插入图片描述
在这里插入图片描述

结论:

auto_ptr的问题:当对象拷贝赋值之后,前面的对象就悬空了。由于缺陷明显,所以很多公司都禁止使用auto_ptr智能指针。

1.4 std::unique_ptr(C++11)

  • unique_ptr的设计思路非常简单,就是不让拷贝和赋值
#include<memory>

class Date
{
public:
	Date()
	{
		cout << "Date()" << endl;
	}
	~Date()
	{
		cout << "~Date" << endl;
	}
	int _year;
	int _month;
	int _day;
};

int main()
{
	unique_ptr<Date> up(new Date);
	unique_ptr<Date> copy(up);

	system("pause");
}

运行失败,失败原因:
在这里插入图片描述

1.5 std::shared_ptr

  • shared_ptr原理:通过引用计数的方法来实现多个shared_ptr对象之间共享资源。例子:宿舍出门的时候总是让最后一个人关门。
    1.shared_ptr在其内部,给每个资源维持了一份计数,用来记录该资源被几个对象共享
  1. 在对象被销毁的时候,就说明这个对象不使用该资源了,对象的引用-1
  2. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源
  3. 如果不是0,说明除了自己还有别人在使用这个资源,不能释放该资源,不然其他对象都会变成野指针。
    代码实现:
int main()
{
	shared_ptr <Date> up(new Date);
	shared_ptr <Date> copy(up);

	cout << "count:" << up.use_count() << endl;
	cout << "count:" << copy.use_count() << endl;

	system("pause");
}

结果:
在这里插入图片描述

2.模拟实现auto_ptr, unique_ptr, shared_ptr

2.1 模拟实现auto_ptr

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

	~AutoPtr()
	{
		if (_ptr)
		{
			delete _ptr;
		}
	}
	//一旦发生拷贝,将ptr中资源转移到当前对象中,然后让ptr与其管理的资源单开联系
	//避免了一个空间被多个对象使用时造成的崩溃问题。
	AutoPtr(AutoPtr<T>& ptr)
		:_ptr(ptr._ptr)//初始化
	{
		ptr._ptr = NULL;//将被拷贝赋值为0
	}

	AutoPtr<T>& operator= (AutoPtr<T>& ptr)
	{
		//日常检查是不是自己为空
		if (this != &ptr)
		{
			if (_ptr)
				delete _ptr;
			//将ptr对象转移到当前对象中
			_ptr = ptr._ptr;
			ptr._ptr == NULL;
		}
		return *this;//operator运算一定要有返回值
	}

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

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

int main()
{
	AutoPtr<Date> ap(new Date);
	AutoPtr<Date> copy(ap);
	ap->_year = 2019;
	system("pause");
}

2.2 模拟实现unique_ptr

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

	~UniquePtr()
	{
		if (_ptr)
		{
			delete _ptr;
		}
	}

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

	T* operator->()
	{
		return _ptr;
	}
private:
	//C++98防拷贝:只声明不实现+声明成私有
	UniquePtr(UniquePtr<T> cosnt &);
	UniquePtr<T> operator= (UniquePtr<T> const &);
	//C++11防拷贝:delete
	UniquePtr(UniquePtr<T> cosnt &) = delete;
	UniquePtr<T> operator= (UniquePtr<T> const &) = delete;
private:
	T* _ptr;
};

C++98的防拷贝有一个缺陷,如果友元存在的话会出现错误。

2.3 模拟实现shared_ptr

简单的实现(网络还没学网络…)

template<class T>
class  SharedPtr
{
public:
	SharedPtr(T* ptr = nullptr)
		:_ptr(ptr)_Rcount(new int(1))
	{
	}

	~SharedPtr()
	{
		if (_ptr)
		{
			delete _ptr;
		}
	}

	SharedPtr(SharedPtr<T>& ptr)
		:_ptr(ptr._ptr)
		, _Rcount(ptr._Rcount)
	{
		++(*_Rcount);//计数空间++
	}

	SharedPtr<T> operator= (SharedPtr<T>& s)
	{
		if (this != &s)
		//if(_ptr != s._ptr)
		{
			//如果被赋值的数计数为1的话,直接将原计数空间和原地址释放
			if (--(*_Rcount) == 0)
			{
				delete _ptr;
				delete _Rcount;
			}
			//将赋值计数空间和赋值地址给到被赋值计数空间和被赋值地址
			_ptr = s._ptr;
			_Rcount = s._Rcount;
			//计数++
			++(*_Rcount);
		}
	}
	T& operator*()
	{
		return *_ptr;
	}

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

private:
	T* _ptr;//指向管理资源的指针
	int* _Rcount;//引用计数
};

猜你喜欢

转载自blog.csdn.net/l477918269/article/details/88926858