lifei_szdz / C / (4条消息)C++智能指针

分享

   

(4条消息)C++智能指针

2019-03-15  lifei_szdz

一、智能指针

1.什么是智能指针?

——是一个类,用来存储指针(指向动态分配对象的指针)。

2.智能指针满足的条件:

1.具有RAII思想
2.能够像指针一样(运算符重载,解引用,指向对象成员)
3.对资源进行封装和管理

RAII思想(资源分配及初始化)

1.定义一个类来封装资源的分配与释放,
2.构造函数中完成资源的分配及初始化;
3.析构函数中完成资源的清理,可以保证资源的正确初始化和释放
4.如果对象是用声明的方式在栈上创建局部对象,那么RAII机制就会正常工作,当离开作用域对象会自动销毁而调用析构函数释放资源。

3.引入智能指针的目的:

代码分析:

void test()
{
	int*_ptr=new int(1);
	if(_ptr)
	{
		throw 1;
	}
	delete _ptr;
}

int main()
{
	try
	{
		test();
	}
	catch(...)
	{}
	return 0;
}

上述代码所示,
1.在test函数中new一个四字节的空间,
2.判断if条件的语句为真,抛出异常
3.main函数直接catch 捕获异常,函数返回0
4.try 执行了直接执行catch,程序结束,以至于没有执行delete_ptr释放空间,导致内存泄漏。

其实在throw前加一个delete语句就可以解决问题,但是代码超级超级多的时候,如果有多个异常抛出,难道我们要写多个delete语句如此麻烦吗?
——智能指针 就可以解决这一问题

个人理解:

1.智能指针是通过基本类型(模板类)指针,构造类的对象,只能指针本身就是一个自定义的对象。
2.当此对象被销毁时,即调用此对象的析构函数,释放此指针。
也就是用栈中的空间来管理堆中的内存。


二、六种常用的智能指针

1. auto_ptr##

auto_ptr事实上是一个类,在构造对象时获取对象的管理权,
无需考虑释放动态内存开辟的空间,在析构函数中直接释放,不会出现内存泄漏的问题。

模拟实现:

//模拟实现auto_ptr
template<class T>
class Auto_ptr
{
public:
	Auto_ptr(T*ptr)//构造函数
		:_ptr(ptr)
	{}
	Auto_ptr(Auto_ptr<T>&ap)//拷贝构造
		:_ptr(ap._ptr)
	{
		ap._ptr = NULL;
	}
	Auto_ptr<T>&operator=(Auto_ptr<T>&ap)//赋值运算符的重载
	{
		if (this != &ap)
		{
			if (_ptr)
			{
				delete _ptr;
			}
			_ptr = ap._ptr;
			ap._ptr = NULL;
		}
		return *this;
	}
	~Auto_ptr()//析构函数
	{
		if (_ptr)
		{
			delete _ptr;
		}
	}

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

private:
	T*_ptr;
};

struct AA
{
	int _a;
	int _b;
};

int main()
{
	Auto_ptr<int> ap(new int(3));
	Auto_ptr<int> ap1(ap);
	Auto_ptr<int> ap2(ap);//一个Auto_ptr被拷贝或赋值后,其已经失去了对原对象的所有权,指为NULL

	Auto_ptr<AA>ap3(new AA);
	ap3->_a = 2;
	ap3->_b = 3;

	cout << &ap << endl;
	cout << &ap1 << endl;
	cout << &ap2 << endl;

	system("pause");
	return 0;
}

内存窗口观察:
这里写图片描述

缺陷:
1.一个指针变量指向的空间不能由两个auto_ptr管理,不然会析构两次,使程序崩溃。(不推荐使用)

//错误
int*ptr=new int(1);
auto_ptr1<int>ap(ptr);
auto_ptr2<int>ap(ptr);

这里写图片描述

2.auto_ptr的拷贝构造,将源指针的管理权交给目标指针,会使得源指针悬空,解引用是会出现很多问题。
这里写图片描述

3.auto_ptr不能用来管理数组,析构函数中用的是delete

//错误
int *ptr=new int[6];
auto_ptr<int>ap(ptr);



2.scoped_ptr

scoped_ptr防拷贝,粗暴的方式(推荐使用)
1.拷贝构造函数和赋值运算符重载函数只声明不实现
2.用private对其进行访问限定,防止在类外定义

模拟实现:

//模拟实现scoped_ptr
template<class T>
class Scoped_ptr
{
public:
	Scoped_ptr(T*ptr)//构造函数
		:_ptr(ptr)
	{}

	~Scoped_ptr()
	{
		delete _ptr;
	}

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

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


private:
	Scoped_ptr(const Scoped_ptr<int>&sp);//拷贝构造
	Scoped_ptr&operator=(const Scoped_ptr<int>&sp);//赋值运算符的重载
	T*_ptr;

};

int main()
{
	Scoped_ptr<int>sp(new int(2));
	system("pause");
	return 0;
}

缺陷:不能进行拷贝构造,管理的对象不能共享所有权,功能不全面。




3.scoped_array

scoped_array 和 scoped_ptr的功能是一样的,只是scoped_array管理的对象是数组,需要重载[]的形式。

模拟实现:


//模拟实现scoped_array
template<class T>
class Scoped_array
{
public:
	Scoped_array(T*ptr)//构造函数
		:_ptr(ptr)
	{}

	~Scoped_array()
	{
		delete[] _ptr;
	}

	T&operator[](size_t i)
	{
		return _ptr[i];
	}

private:
	Scoped_ptr(const Scoped_ptr<int>&sa);//拷贝构造
	Scoped_ptr&operator=(const Scoped_ptr<int>&sa);//赋值运算符的重载
	T*_ptr;

};

int main()
{
	Scoped_array<int>sa(new int[2]);
	system("pause");
	return 0;
}



4.shared_ptr

加入了引用计数,从而很好的规避了auto_ptr 释放两次空间,调两次析构的情况

模拟实现:

//模拟实现shared_ptr
template<class T>
class Shared_ptr
{
public:
	Shared_ptr(T*ptr)//构造函数
		:_ptr(ptr)
		,_pCount(new int(1))
	{}

	Shared_ptr&(const Shared_ptr&sp)
		: _ptr(sp._ptr)
		; _pCount(sp._pCount)
	{
		(*_pCount)++;
	}
	Shared_ptr&operator=(Shared_ptr<T>&sp)
	{
		if (this != sp)
		{
			if (--(*pCount) == 0)
			{
				delete _ptr;
				delete _pCount;
			}

			_ptr = sp._ptr;
			_pCount - sp._pCount;
			(*_pCount)++;
		}
		return*this;
	}

	~Shared_ptr()
	{
		if (--(*pCount) == 0)
		{
			delete _ptr;
			delete _pCount;
		}
	}

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

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

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



5.shared_array

shared_array 和 shared_ptr的功能是一样的,只是shared_array管理的对象是数组,需要重载[]的形式。
//模拟实现shared_ptr
template<class T>
class Shared_ptr
{
public:
	Shared_ptr(T*ptr)//构造函数
		:_ptr(ptr)
		,_pCount(new int(1))
	{}

	Shared_ptr&(const Shared_ptr&sp)
		: _ptr(sp._ptr)
		; _pCount(sp._pCount)
	{
		(*_pCount)++;
	}
	Shared_ptr&operator=(Shared_ptr<T>&sp)
	{
		if (this != sp)
		{
			if (--(*pCount) == 0)
			{
				delete[] _ptr;
				delete _pCount;
			}

			_ptr = sp._ptr;
			_pCount - sp._pCount;
			(*_pCount)++;
		}
		return*this;
	}

	~Shared_ptr()
	{
		if (--(*pCount) == 0)
		{
			delete _ptr;
			delete _pCount;
		}
	}

	T&operator[]()
	{
		return _ptr[i];
	}

	
	int Count()
	{
		return *_pCount;
	}

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

但是,shared_ptr也有一个致命的缺点,就是会出现循环引用
Shared_ptr 会出现循环引用的情况:
这里写图片描述

//测试shared_ptr代码
struct LinkList
{
	int _data;
	Shared_ptr <LinkList>_next;
	Shared_ptr <LinkList>_prev;
	LinkList(int x)
		:_data(x)
		,_next(NULL)
		,_prev(NULL)
	{}
	~LinkList()
	{}
};

int main()
{
	//定义两个对象(两个节点)
	Shared_ptr<LinkList>cur(new LinkList(1));
	Shared_ptr<LinkList>next(new LinkList(2));

	cur->_next = next;  // next 类型是shared_ptr,在此处生成一个匿名对象(week_ptr 类型),再进行赋值 
	next->_prev = cur;

	cout << "cur->pCount: " << cur.Count() << endl;
	cout << "next->pCount: " << next.Count() << endl;

	system("pause");
	return 0;


}


循环引用的结果(造成每个节点都有两个计数器)
这里写图片描述



6.week_ptr——为了解决循环引用的问题

(和shared_ptr配合使用)
这里写图片描述


//模拟实现shared_ptr
template<class T>
class Shared_ptr
{
public:
	Shared_ptr(T*ptr)//构造函数
		:_ptr(ptr)
		,_pCount(new int(1))
	{}

	Shared_ptr(const Shared_ptr<T>&sp)//拷贝构造
		: _ptr(sp._ptr)
		, _pCount(sp._pCount)
	{
		(*_pCount)++;
	}

	Shared_ptr<T>&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;
	}

	~Shared_ptr()
	{
		if (--(*_pCount) == 0)
		{
			delete _ptr;
			delete _pCount;
		}
	}

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

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

//protected:
	T* _ptr;
	int *_pCount;
};



template<class T>
class Week_ptr
{
public:
	Week_ptr()
		:_ptr(NULL)
	{}

	Week_ptr(const Shared_ptr<T> &sp)
		:_ptr(sp._ptr)
	{}

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

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

private:
	T*_ptr;
};

//测试week_ptr
struct LinkList
{
	int _data;
	Week_ptr<LinkList>_next;
	Week_ptr<LinkList>_prev;
	LinkList(int x)
		:_data(x)
		,_next(NULL)
		,_prev(NULL)
	{}

	~LinkList()
	{}
};

int main()
{
	//定义两个对象(两个节点)
	Shared_ptr<LinkList>cur(new LinkList(3));
	Shared_ptr<LinkList>next(new LinkList(4));

	cur->_next = next;
	next->_prev = cur;

	cout << "cur->pCount: " << cur.Count() << endl;
	cout << "next->pCount: " << next.Count() << endl;

	system("pause");
	return 0;
	
}

补充说明week_ptr的工作原理:

这里写图片描述


这里写图片描述

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。

    来自: lifei_szdz > 《C 》

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多
    喜欢该文的人也喜欢 更多

    ×
    ×

    ¥.00

    微信或支付宝扫码支付:

    开通即同意《个图VIP服务协议》

    全部>>