智能指针

智能指针

概念

简单来说智能指针就是一个类似于指针的玩意,帮我们管理一个对象,不需要我们去手动释放指针所指对象。

智能指针(英语:Smart pointer)是一种抽象的数据类型。在程序设计中,它通常是经由类模板来实现,借由模板来达成泛型,借由类别的析构函数来达成自动释放指针所指向的存储器或对象。

智能指针一般会遵循以下两点:

  1. 遵循RAII思想——资源分配即初始化
  2. 重载*->运算符,有些针对数组的智能指针也会重载[]

RAII思想

RAII是英文Resource Acquisition Is Initialization的缩写,指的是资源分配即初始化。

RAII要求,资源的有效期与持有资源的对象的生命期严格绑定,即由对象的构造函数完成资源的分配(获取),同时由析构函数完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现资源泄露问题。

重重载*->运算符

因为对于指针而言,指针访问一个对象内部成员通常是使用解引用*.或者->来访问,比如指针p指向的一个对象中有一个成员num,那么我们访问使用的是(*p).num或者p->num,但是智能指针本身是个类,所以重载*->是为了让和指针一样去访问成员,方便使用。

重载->运算符

我们重载运算符的时候,在使用过程中运算符会被消耗掉,比如重载**p就相当于operator*(),在智能指针中返回的是该智能指针所指对象。
类比,如果我们用->,实际是相当于operator->(),此时这个运算符就被消耗了,返回一个智能指针所指对象的指针,问题是如果我们要接着访问对象内成员的话,应该还需要一个->,也就是说我们在用的时候应这么用p->->member,但这样极其不便于书写和阅读习惯,因此在这里编译器对其处理成只需要一个->就能访问成员,这里是比较特殊的地方。

为什么需要智能指针

在C++中由于有异常这种扰乱程序执行流程的东西,尽管我们写了new之后马上跟了delete,但是函数里面new了之后还没执行到delete就抛异常跳出函数,导致对象没有被释放造成内存泄漏问题,防不胜防,或者我压根就忘了释放,这个时候就是智能指针登场的时候了。

智能指针本身是一个类,由于类的对象创建时系统会自动调用构造函数,而当对象生命周期结束时,系统会自动调用析构函数,让对象能够自动释放,这样就无需我们手动去管理,也不会出现因执行流被打断造成的内存泄漏问题。

智能指针的历史

  1. auto_ptr:C++98标准中提供了该类的智能指针,但是这个智能指针是个残疾,当使用另一个auto_ptr拷贝时,原指针会被置空,严重影响体验,公司不让用。
  2. scoped_ptr:这个智能指针由Boost的大佬们实现,原因是auto_ptr就是个残废,于是有了这个指针,scoped_ptr是auto_ptr的防拷贝版本,解决了auto的不足,日常使用满足需求,公司推荐使用。
  3. shared_ptr:该指针也是Boost库中的指针,这个版本引入了引用计数实现共享指针,但是会有智能指针相互循环引用的问题,在特定场景会内存泄漏,公司推荐使用。
  4. weak_ptr:Boost库的智能指针,用于解决shared_ptr循环引用的问题,可以接收一个shared_ptr的对象,并指向shared_ptr指向的对象,引用计数独立。
  5. unique_ptr/shared_ptr/weak_ptr:C++11标准提供,由于Boost库已经实现的很成熟,因此C++11实现的和其很类似,使用也基本没有差别,其中unique_ptr就是Boost库中的scoped_ptr。

shared_ptr的循环引用

当有如下类

struct ListNode
{
	shared_ptr<ListNode> _next;
	shared_ptr<ListNode> _prev;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

如果存在两个指针p1和p2

  1. p1->_next = p2;
  2. p2->_prev = p1;
    此时存在循环引用问题,析构p1的前提是p2->_prev要析构(因为引用计数为2,光析构一个不行),析构p2的前提是p1->_next要析构,这就很麻烦,到了最后析构的时候,引用计数只减了1,两个对象还是没析构掉,反而出现了内存泄漏。。

如何解决

Boost库提供了一种智能指针叫weak_ptr专门解决这类问题,只需要将上述的结构体定义如下,便能解决,由于weak_ptr不影响shared_ptr的引用计数,所以释放时会正常释放

struct ListNode
{
	weak_ptr<ListNode> _next;
	weak_ptr<ListNode> _prev;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

库中的智能指针使用

单个对象

单个对象比较简单,不多赘述,看代码即可

struct TestClass
{
public:
	TestClass()
	{
		cout << "TestClass()" << endl;
	}
	~TestClass()
	{
		cout << "~TestClass()" << endl;
	}
private:
	int num;
};

void test()
{

	unique_ptr<TestClass> up(new TestClass);

	shared_ptr<TestClass> sp1(new TestClass);
	shared_ptr<TestClass> sp2(sp1);
	cout << sp1.use_count() << endl;
	cout << sp2.use_count() << endl;
	cout << sp1.get() << endl;
	cout << sp2.get() << endl;
}

多个对象(数组)或特殊处理类型(FILE指针、malloc/free)

多个对象如果默认不处理,最后只会析构一个对象,甚至可能出错,特殊类型也不能用delete简单处理,比如FILE指针必须用fclose处理,这里就涉及到仿函数处理特殊类型了,自定义一个类,重载(),在创建智能指针时把其对象作为第二个参数传入,处理即按照仿函数处理析构,第二个参数就是给我们自定析构用的,默认用delete,自定可以自定析构方式,如delete[]、free、fclose等,实现自动管理。

template <class T>
class DeleteArray
{
	void operator()(T* ptr)
	{
		cout << "DeleteArray delete array" << endl;
		delete[] ptr;
	}
};

template <class T>
class DeleteFILE
{
	void operator()(T* ptr)
	{
		cout << "DeleteArray delete array" << endl;
		fclose(ptr);
	}
};

void testArray()
{
	unique_ptr<TestClass[]> up_group(new TestClass[10]);

	//shared智能指针管理数组,自定义删除模式
	shared_ptr<TestClass> sp_group(new TestClass[10], DeleteArray<TestClass>());
	//管理特殊类型
	shared_ptr<FILE> file(fopen("text.txt", "w"), DeleteFILE<FILE>());
}
仿函数

仿函数就是重载了(),比如类A重载了一个函数,有两个int参数,如void operator()(int x1, int x2);,这个时候我们就可以这么调用这个成员,A(1, 2);,由于其调用方式很像函数的调用,但本质是重载了运算符(),因此称之为仿函数。

线程安全问题

使用shared_ptr时会有一个安全问题,由于shared_ptr是可以拷贝的,指向内容可以被多个智能指针访问,如果这些指针在不同的线程内,那么会出现线程安全问题,引用计数也是如此,可能存在短时间内被两个指针访问修改的情况,那么就会出问题。

shared_ptr的线程安全可以分为两层:

  1. 引用计数线程安全由指针维护,无需我们多管
  2. 指针所指向内容需要用户自己去维护,这样就需要我们对临界资源进行控制,linux下使用信号量或者互斥锁,Windows的话。。不清楚

智能指针的简单实现

auto_ptr

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

	AutoPtr(AutoPtr<T>& sp)
		:_ptr(sp._ptr)
	{
		sp._ptr = NULL;
	}
	
	~AutoPtr()
	{
		delete _ptr;
	}
	
	AutoPtr<T>& operator=(AutoPtr<T>& sp)
	{
		//在auto_ptr中,如果赋值就把原指针直接给置空了,这是一件很蠢的事情
		_ptr = sp._ptr;
		sp._ptr = NULL;
	}
	
	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};
//这个版本没有测试用例

scoped_ptr

防拷贝的实现使用delete关键字,删除拷贝构造和operator=(),表明函数已经被删除,无法调用。

template <class T>
class ScopedPtr
{
public:
	ScopedPtr(T* ptr = new T)
		:_ptr(ptr)
	{}

	~ScopedPtr()
	{
		delete _ptr;
	}

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

	T* operator->()
	{
		return _ptr;
	}
	ScopedPtr(ScopedPtr<T>& sp) = delete;
	ScopedPtr<T>& operator=(ScopedPtr<T>& sp) = delete;
	ScopedPtr<T>& operator=(T* ptr) = delete;
protected:
	T* _ptr;
};

void testScopePtr()
{
	ScopedPtr<string> sp;
	//ScopedPtr<string> sp1(sp);//错误,拷贝构造为私有无法调用
	//sp1 = sp;//错误,=运算符重载后为私有,无法调用
	*sp = "hello world";
	cout << *sp << endl;
	sp->at(0) = 'H';//调用string类的at
	cout << *sp << endl;
}

shared_ptr

template <class T>
class SharedPtr
{
	template <class>
	friend class WeakPtr;
public:
	SharedPtr(T* ptr = NULL)
		:_ptr(ptr)
		,_count(new size_t(1))
	{}

	SharedPtr(SharedPtr<T>& sp)
		:_ptr(sp._ptr)
		, _count(sp._count)
	{
		(*_count)++;
	}

	~SharedPtr()
	{
		if (--(*_count) == 0)
		{
			delete _ptr;
			delete _count;
		}
	}

	SharedPtr<T>& operator=(SharedPtr<T>& sp)
	{
		if (_ptr != sp._ptr)
		{
			if (--(*_count) == 0)
			{
				delete _ptr;
				delete _count;
			}

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

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

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

void testSharedPtr()
{
	SharedPtr<string> sp = new string;
	SharedPtr<string> sp1;//可以使用
	sp1 = sp;//可以使用
	*sp = "hello world";
	cout << *sp << endl;
	sp->at(0) = 'H';//调用string类的at
	cout << *sp1 << endl;
}

struct List
{
	SharedPtr<List> _next;
	SharedPtr<List> _prev;
	List()
		:_next(NULL)
		, _prev(NULL)
	{}
	~List()
	{
		cout << "~List()" << endl;
	}
};

void testSharedPtrCircularReference()
{
	//这里压根就调不到析构函数,因为引用计数互相引用后为2
	//出作用域后计数为1,还没达到析构条件
	//循环引用问题
	SharedPtr<List> p1 = new List();
	SharedPtr<List> p2 = new List();
	p1->_next = p2;
	p2->_prev = p1;
}

weak_ptr

弱指针用于处理循环引用的问题

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

	WeakPtr(const SharedPtr<T>& sp)
		:_ptr(sp._ptr)
	{}

	WeakPtr(WeakPtr<T>& wp)
		:_ptr(wp._ptr)
	{}

	WeakPtr<T>& operator=(SharedPtr<T>& sp)
	{
		_ptr = sp._ptr;
		return *this;
	}

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

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

struct ListNode
{
	WeakPtr<ListNode> _next;
	WeakPtr<ListNode> _prev;

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

// 循环引用
void TestCycle()
{
	SharedPtr<ListNode> n1 = new ListNode;
	SharedPtr<ListNode> n2 = new ListNode;

	n1->_next = n2;
	n2->_prev = n1;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值