C++——智能指针

29 篇文章 1 订阅

本章代码Gitee仓库:智能指针

1. 为什么要有智能指针

C++引入了异常体系之后,对于普通的指针的内存释放是较困难的,例如:

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
	{
		throw invalid_argument("error: DIV_ZERO");
	}
	return a / b;
}

void func()
{
	int* p = new int(10);
	try
	{
		div();
	}
	catch (...)
	{
		delete p;
		throw;
	}

	delete p;
}

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

这里如果指针一多,那么对于每个异常的处理都需要考虑到指针的释放,这个操作还很繁琐的,而且代码很不优雅!

使用智能指针能较好的预防内存泄漏

2. RAII机制

为了解决上面的问题,设计了一个类来管理指针:

template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr = nullptr)
		:ptr_(ptr)
	{}
	~SmartPtr()
	{
		if(ptr_)
			delete ptr_;
	}
private:
	int* ptr_;
};

这里利用了构造和析构的特性,对象初始化自动调用构造函数,出了作用域自动调用析构函数,这就不需要我们手动的delete

这就是 RAII(Resource Acquisition Is Initialization)机制:利用对象生命周期控制程序资源,将需要管理的资源交给一个对象,这样我们就无需显示释放资源且对象所需资源在生命周期内始终有效。

3. 智能指针原理

引入RAII机制之后,解决了申请释放的问题,但是还无法像指针一样操作,例如指针解引用和指向空间的内容,这里就需要重载一下*->

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_;
		}
	}
private:
	T* ptr_;
};

以上就是智能指针的原理:

  1. 利用RAII机制管理
  2. 重载*->,让其能像指针一样操作
  3. 拷贝问题

但是这里还没完,这里还是有一些问题的,例如赋值操作:

image-20240313195400769

这里赋值操作导致了指向同一块空间,最后释放了2次,而另一块空间内存泄漏。

为了解决这一系列的隐含问题,便出现了我们现在的智能指针,智能指针发展:

  1. C++98:auto_ptr(管理权转移)
  2. C++11:unique_ptr(禁止拷贝)、shared_ptr(支持拷贝,但有循环引用问题)、weak_ptr(不支持RAII机制,专门解决shared_ptr循环引用问题)

4. auto_ptr (c++98)

auto_ptr是一个较为失败的设计:

class A
{
public:
	A(int a = 0)
		:a_(a)
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int a_;
};

int main()
{
	auto_ptr<A> ap1(new A(1));
	auto_ptr<A> ap2(ap1);
	return 0;
}

GIF 2024-3-13 20-57-10

这里拷贝构造之后,将被拷贝的对象资源管理权直接转给了拷贝对象,导致被拷贝对象悬空,访问就会出问题

这里不能理解成移动构造,移动构造转移的是将亡值的资源,而auto_ptr是将正常的资源直接转移了

auto_ptr底层简易模拟实现:

namespace myptr
{
	template<class T>
	class auto_ptr
	{
		//RAII
		//像正常指针
	public:
		auto_ptr(T* ptr = nullptr)
			:ptr_(ptr)
		{}
		//拷贝构造
		auto_ptr(auto_ptr<T>& ap)
			:ptr_(ap.ptr_)	//管理器转让
		{
			ap.ptr_ = nullptr;	//将自己置空
		}
		T& operator*()
		{
			return *ptr_;
		}

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

		~auto_ptr()
		{
			if (ptr_)
			{
				cout << "delete: " << ptr_ << endl;
				delete ptr_;
			}
		}
	private:
		T* ptr_;
	};
};

5. unique_ptr (c++11)

c++11引入新的智能指针,是“借鉴”boost库的,unique_ptrboost库中叫做scoped_ptr,其他2个名字没变

Boost库_百度百科 (baidu.com)

auto_ptr拷贝有问题,而unique_ptr的解决方案十分简单粗暴,直接禁掉拷贝

class A
{
public:
	A(int a = 0)
		:a_(a)
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int a_;
};

int main()
{
	unique_ptr<A> up1(new A(10));
    //error
	//unique_ptr<A> up2(up1);
	return 0;
}

image-20240313221305517

unique_ptr底层简易模拟实现:

	template<class T>
	class unique_ptr
	{
		//RAII
		//像正常指针
	public:
		unique_ptr(T* ptr = nullptr)
			:ptr_(ptr)
		{}
		//拷贝构造
		unique_ptr(unique_ptr<T>& up) = delete;	//禁拷贝构造
		unique_ptr<T>& operator=(unique_ptr<T>& up) = delete;	//禁赋值运算符重载
		T& operator*()
		{
			return *ptr_;
		}

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

		~unique_ptr()
		{
			if (ptr_)
			{
				cout << "delete: " << ptr_ << endl;
				delete ptr_;
			}
		}
	private:
		T* ptr_;
	};

6. shared_ptr (c++11)

shared_ptr是支持拷贝,通过引用计数的方式实现资源共享:

最后一个走的人锁门

class A
{
public:
	A(int a = 0)
		:a_(a)
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int a_;
};

int main()
{
	shared_ptr<A> sp1(new A(10));
	shared_ptr<A> sp2(new A(20));
	shared_ptr<A> sp3(sp1);
	shared_ptr<A> sp4 = sp2;

	return 0;
}
//输出:
//	A()
//	A()
//	~A()
//	~A()

image-20240313230943763

6.1 shared_ptr线程安全问题

这个引用计数是一个临界资源,当多个线程同时访问的时候,会引发线程安全问题,这里就需要加锁

	template<class T>
	class shared_ptr
	{
		//RAII
		//像正常指针
	public:
		shared_ptr(T* ptr = nullptr)
			:ptr_(ptr), pcount_(new int(1)), pmtx_(new mutex)
		{}

		//拷贝构造
		shared_ptr(const shared_ptr<T>& sp)
			:ptr_(sp.ptr_), pcount_(sp.pcount_), pmtx_(sp.pmtx_)
		{
			AddCount();
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//防止自己给自己赋值
			if (ptr_ == sp.ptr_)
				return *this;

			ptr_ = sp.ptr_;
			pcount_ = sp.pcount_;
			pmtx_ = sp.pmtx_;
			AddCount();
			return *this;
		}

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

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

		void Release()
		{
			pmtx_->lock();
			bool deleteFlag = false;
			if (--(*pcount_) == 0)
			{
				delete ptr_;
				delete pcount_;
				deleteFlag = true;
			}
			pmtx_->unlock();
			if (deleteFlag)	delete pmtx_;
		}

		void AddCount()
		{
			pmtx_->lock();
			++(*pcount_);
			pmtx_->unlock();
		}

		T* get()
		{
			return ptr_;
		}

		int use_count()
		{
			return *pcount_;
		}

		~shared_ptr()
		{
			Release();
		}
	private:
		T* ptr_;
		int* pcount_;	//指针,多个智能指针访问同一个计数器
		mutex* pmtx_;	//指针,多个智能指针访问同一把锁
	};

shared_ptr本身是线程安全的,因为计数有加锁保护!

shared_ptr管理的对象,不一定是线程安全的!

struct Date
{
	int year_ = 0;
	int month_ = 0;
	int day_ = 0;
};

void SharePtrFunc(myptr::shared_ptr<Date>& sp, size_t n, mutex& mtx)
{
	cout << sp.get() << endl;
	int begin = clock();
	for (size_t i = 0; i < n; i++)
	{
		//std::shared_ptr<Date> copy(sp);
		myptr::shared_ptr<Date> copy(sp);
		mtx.lock();	//加锁保护
		{
			copy->year_++;
			copy->month_++;
			copy->day_++;
		}
		//cout << i << endl;
		mtx.unlock();

	}
	int end = clock();
	//cout << end - begin << endl;
}

int main()
{
	//std::shared_ptr<Date> p(new Date);
	myptr::shared_ptr<Date> p(new Date);
	cout << p.get() << endl;
	const size_t sz = 500000;
	mutex mtx;
	thread t1(SharePtrFunc, ref(p), sz, ref(mtx));
	thread t2(SharePtrFunc, ref(p), sz, ref(mtx));

	t1.join();
	t2.join();
	cout << p.use_count() << endl;
	cout << p->year_ << endl;
	cout << p->month_ << endl;
	cout << p->day_ << endl;
}

6.2 shared_ptr循环引用

来看这段代码:

class A
{
public:
	A(int a = 0)
		:a_(a)
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int a_;
};

struct Node
{
	A val_;
	shared_ptr<Node> next_;
	shared_ptr<Node> prev_;
};

int main()
{
	shared_ptr<Node> sp1(new Node);
	shared_ptr<Node> sp2(new Node);
	sp1->next_ = sp2;
	sp2->prev_ = sp1;
	return 0;
}
//输出:
//A()
//A()

这里的sp1sp2都没有析构,这就是shared_ptr的循环引用问题:

image-20240314130522193

这属于shared_ptr的一个缺陷,要解决这个问题就得采用weak_ptr

6.3 定制删除器

shared_ptr底层默认析构是delete ptr,要是对于数组指针或者是malloc出来的,就无法析构,所以shared_ptr构造还是支持了定制删除器(仿函数)。

class A
{
public:
	A(int a = 0)
		:a_(a)
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << a_ << "~A()" << endl;
	}
private:
	int a_;
};

template<class T>
struct DeleteArr
{
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};

int main()
{
	shared_ptr<A> sp1(new A[5], DeleteArr<A>());	//返函数
	cout << endl;
	shared_ptr<A> sp2((A*)malloc(sizeof(A)), [](A* ptr) {free(ptr); });	//lambda表达式
	return 0;
}

image-20240314140519388

shared_ptr底层简易模拟实现:

这个可以指向同一块空间,而且不会被多次释放,采用引用计数的方式。

这里的引用计数,每个申请资源都有一份

image-20240313230538296

	template<class T>
	class shared_ptr
	{
		//RAII
		//像正常指针
	public:
		shared_ptr(T* ptr = nullptr)
			:ptr_(ptr), pcount_(new int(1)), pmtx_(new mutex)
		{}

		template<class D>
		shared_ptr(T* ptr, D del)
			: ptr_(ptr), pcount_(new int(1)), pmtx_(new mutex), del_(del)
		{}

		//拷贝构造
		shared_ptr(const shared_ptr<T>& sp)
			:ptr_(sp.ptr_), pcount_(sp.pcount_), pmtx_(sp.pmtx_)
		{
			AddCount();
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//防止自己给自己赋值
			if (ptr_ == sp.ptr_)
				return *this;

			ptr_ = sp.ptr_;
			pcount_ = sp.pcount_;
			pmtx_ = sp.pmtx_;
			AddCount();
			return *this;
		}

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

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

		void Release()
		{
			pmtx_->lock();
			bool deleteFlag = false;
			if (--(*pcount_) == 0)
			{
				//delete ptr_;
				del_(ptr_);
				delete pcount_;
				deleteFlag = true;
			}
			pmtx_->unlock();
			if (deleteFlag)	delete pmtx_;
		}

		void AddCount()
		{
			pmtx_->lock();
			++(*pcount_);
			pmtx_->unlock();
		}

		T* get() const
		{
			return ptr_;
		}

		int use_count() const
		{
			return *pcount_;
		}

		~shared_ptr()
		{
			Release();
		}
	private:
		T* ptr_;
		int* pcount_;	//指针,多个智能指针访问同一个计数器
		mutex* pmtx_;	//指针,多个智能指针访问同一把锁
		function<void(T*)> del_ = [](T* ptr) {delete ptr; };	//定制删除器
	};

7. weak_ptr (c++11)

weak_ptr不是RAII机制,它的出现是专门用来解决shared_ptr的循环引用问题,不是单独使用的,是搭配着shared_ptr使用:

class A
{
public:
	A(int a = 0)
		:a_(a)
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << a_ << "~A()" << endl;
	}
private:
	int a_;
};
struct Node
{
	A val_;
	weak_ptr<Node> next_;
	weak_ptr<Node> prev_;
};

int main()
{
	shared_ptr<Node> sp1(new Node);
	shared_ptr<Node> sp2(new Node);
	sp1->next_ = sp2;
	sp2->prev_ = sp1;
	return 0;
}

这里shared_ptr能赋值给weak_ptr,是因为weak_ptr重载了=运算符:

image-20240314131756521

这里解决循环的引用的原理就是不增加引用计数,不参与资源的释放管理

weak_ptr底层简易模拟实现:

	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:ptr_(nullptr)
		{}
		weak_ptr(const shared_ptr<T>& sp)
			:ptr_(sp.get())
		{}
		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			ptr_ = sp.get();
			return *this;
		}
		T& operator*()
		{
			return *ptr_;
		}
		T* operator->()
		{
			return ptr_;
		}
	private:
		T* ptr_;
	};

8. C/C++动态内存

C语言——动态内存管理

C++动态内存管理

  • 33
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

加法器+

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值