什么是智能指针

如下代码存在什么问题

#include <iostream>
#include <vector>
#include <string>
using namespace std;

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;
};
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void func()
{
	pair<string, string>* p1 = new pair<string, string>;
	//pair<string, string>* p2 = new pair<string, string>;

	try
	{
		div();
	}
	catch (...)
	{
		delete p1;
		throw;
	}
	delete p1;
}

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

上面的指针只有一个,还好管理,如果指针有三个呢?如果第二个开辟失败,那就得释放第一个。这里要分类讨论,每种情况还都不一样。那如果有10个呢?这要套多少个try..catch语句。像这种情况的内存泄漏,是很可怕的。

pair<string, string>* p1 = new pair<string, string>;
pair<string, string>* p2 = new pair<string, string>;
pair<string, string>* p3 = new pair<string, string>;

使用智能指针管理指针

#include <iostream>
#include <vector>
#include <string>
using namespace std;

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;
};
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void func()
{
	//使用SmartPtr类对象,来管理指针的生命周期 只要出了作用域 自动调用析构函数 释放内存
	SmartPtr<pair<string, string>> sp1(new pair<string, string>{ "byte","字节" });
	SmartPtr<string> sp2(new string{ "byte" });
	cout << sp1->first << "-" << sp1->second << endl;
	cout << *sp2 << endl;
	div();
}

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

RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内 存、文件句柄、网络连接、互斥量等等)的简单技术。

智能指针采取RAII思想管理指针变量,出了作用域自动调用析构函数,释放内存。SmartPtr是模拟智能指针定义的类,他的一切功能都是模拟指针实现的。像指针的operator*和operator->都要重载实现,这里拷贝构造和赋值重载要实现浅拷贝功能,和指针一样,指针就是浅拷贝。

Boost库

Boost 库是一个由 C++ 社区驱动、开源且免费的高质量 C++ 通用库集合,旨在扩展 C++ 标准库的功能,填补标准库在特定领域的空白,并为 C++ 开发者提供工业级、可移植、高效的工具组件。它被广泛视为 “C++ 标准库的试验场”—— 许多 Boost 库(如shared_ptrthreadregex等)已被正式纳入 C++11 及后续标准,成为 C++ 标准的核心组成部分

智能指针发展历史:

auto_ptr c++98 吐槽最多,很多地方禁止使用
unique_ptr c++11 来自Boost库
shared_ptr c++11 来自Boost库
weak_ptr c++11 来自Boost库

Boost库智能指针:

Boost库 c++标准
scoped_ptr unique_ptr
shared_ptr shared_ptr
weak_ptr weak_ptr

auto_ptr:

auto_ptr这一版本的智能指针,实现最简单,毛病最严重,会产生悬空问题。

template<class T>
class auto_ptr
{
public:
	auto_ptr(T* ptr)
		:_ptr(ptr)
	{ }
	~auto_ptr()
	{
		if (_ptr) delete _ptr;
	}
	T& operator*() //返回引用
	{
		return *_ptr;
	}
	T* operator->() //返回指针
	{
		return _ptr;
	}
	//拷贝构造 管理权转移 会导致悬空
	auto_ptr(auto_ptr<T>& ap)
		:_ptr(ap._ptr)
	{
		ap._ptr = nullptr;
	}
private:
	T* _ptr;
};
class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "int a = 0" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
//private:
	int _a;

};
void auto_ptr_test()
{
	//c++98 很多地方 明确禁止使用auto_ptr 
	byte::auto_ptr<A> ap1(new A(1));
	byte::auto_ptr<A> ap2(new A(2));

	//管理权转移 拷贝时 会把拷贝对象的资源管理权转移给拷贝对象
	//导致被拷贝对象悬空 访问就会出问题
	byte::auto_ptr<A> ap3(ap1);

	//崩溃
	ap1->_a++;
}

这里没有模拟实现operator=,他和拷贝构造是一样的。都会造成被拷贝对象悬空,内存泄漏。于是我们的unique_ptr就诞生了,他解决悬空问题。

unique_ptr:

	template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{
		}
		~unique_ptr()
		{
			if (_ptr) delete _ptr;
		}
		T& operator*() //返回引用
		{
			return *_ptr;
		}
		T* operator->() //返回指针
		{
			return _ptr;
		}
		//防拷贝
		unique_ptr(unique_ptr<T>& up) = delete;
		unique_ptr<T>& operator=(unique_ptr<T>& up) = delete;
	private:
		T* _ptr;
	};

他的解决方案就是禁止使用拷贝构造和赋值,和他的名字一样,unique_ptr,唯一的指针,不允许拷贝和赋值。但是,指针的拷贝和赋值是再正常不过的需求了,总会需要的。于是c++委员会就又搞成了个shared_ptr,允许你拷贝构造和赋值。

shared_ptr:

shared_ptr采取一种指针变量记录被拷贝和赋值的次数,只有这个变量指向的次数变成0才可以析构称为引用计数,和java的静态变量count类似。但不过这里不可以采取静态变量,因为静态变量属于每个对象和类,我们只有被拷贝/赋值的对象才能记录。

template<class T>
class shared_ptr
{
public:
	//function<void<T*>> _del 包装器
	template<class D>
	shared_ptr(T* ptr,D del = _del)
		:_ptr(ptr)
		,_pcount(new int(1))
		,_del(del)
	{
	}

	shared_ptr(T* ptr)
		:_ptr(ptr)
		, _pcount(new int(1))
	{
	}
	~shared_ptr()
	{
		if (--(*_pcount) == 0)
		{
			_del(_ptr);
			delete _pcount;
		}
	}
	T& operator*() //返回引用
	{
		return *_ptr;
	}
	T* operator->() //返回指针
	{
		return _ptr;
	}
	//
	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)
		{
			//引用计数为0 析构自己
			if (--(*_pcount) == 0)
			{
				delete _pcount;
				delete _ptr;
			}
			_ptr = sp._ptr;
			_pcount = sp._pcount;
			(*_pcount)++;//引用计数++
		}
		return *this;
	}
	int use_count()
	{
		return *_pcount;
	}
	T* get()
	{
		return _ptr;
	}
private:
	T* _ptr;
	int* _pcount;
	function<void(T*) > _del = [](T* ptr) {delete ptr; };
};

shared_ptr会自己释放掉被拷贝对象(ccount等于0),但是shared_ptr也不是十全十美,他会产生循环引用,导致内存泄漏。

struct Node
{
	A _val;
	byte::shared_ptr<Node> _next;
	byte::shared_ptr<Node> _prev;
	~Node()
	{
		cout << "~Node" << endl;
	}
};
void cir_ref()
{
	byte::shared_ptr<Node> sp1(new Node);
	byte::shared_ptr<Node> sp2(new Node);
	sp1->_next = sp2;
	sp2->_prev = sp1;
}

类似于套娃,自己存储了个自己,因为count是2,导致并没有释放内存。那咋办呢?于是weak_ptr就产生了,弱指针,不会记录count的更迭。

weak_ptr:

template<class T>
class weak_ptr
{
public:
	weak_ptr(T* ptr = nullptr)
		:_ptr(ptr)
	{}
	//不显式定义析构函数
	T& operator*() //返回引用
	{
		return *_ptr;
	}
	T* operator->() //返回指针
	{
		return _ptr;
	}
	weak_ptr(shared_ptr<T>& sp)
		:_ptr(sp.get())
	{}
	weak_ptr<T>& operator=(shared_ptr<T>& sp)
	{
		_ptr = sp.get();
		return *this;
	}
private:
	T* _ptr;
};

循环引用的问题就是内部定义了个自己的类对象而已,导致count多加了。weak_ptr就不定义指针count变量,不参加对象的释放,只参与对象的管理。weak_ptr并不能算独立的智能指针,他是伴随shared_ptr而产生的,他的拷贝构造甚至都没有提供weak_ptr,但是却提供了shared_ptr版本的拷贝构造。所以weak_ptr其实使用往往都是和shared_ptr搭配使用。

struct Node
{
	A _val;
	byte::weak_ptr<Node> _next;
	byte::weak_ptr<Node> _prev;
	~Node()
	{
		cout << "~Node" << endl;
	}
};
void cir_ref()
{
	byte::shared_ptr<Node> sp1(new Node);
	byte::shared_ptr<Node> sp2(new Node);
	sp1->_next = sp2;
	sp2->_prev = sp1;
}

删除器

shared_ptr和unique_ptr都用删除器,本质都是仿函数。shared_ptr的删除器设计在构造函数里,unique_ptr的删除器直接就设计在类模板里。

//仿函数 删除器
template<class T>
struct DeleteArray
{
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};
void Del_test()
{
	byte::shared_ptr<A> sp(new A);
	byte::shared_ptr<A> sp1(new A[5], DeleteArray<A>());
	byte::shared_ptr<A> sp2((A*)malloc(sizeof(A)), [](A* ptr) {free(ptr); });
	byte::shared_ptr<FILE> sp3(fopen("SmartPtr_Test.cpp","r"),[](FILE* ptr) {fclose(ptr); });
}

int main()
{
	Del_test();
	return 0;
}

删除器在实际中运用比较多

智能指针总结

智能指针 版本 特性
auto_ptr c++98 建议不用
unique_ptr c++11 不使用拷贝和赋值场景,建议使用
shared_ptr c++11 几乎都可以使用,小心循环引用
weak_ptr c++11 解决shared_ptr循环引用问题,不能单独构建资源,根本没提供对应的构造

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐