自学C++ day13 智能指针

// 智能指针!
// 内存泄露:
// 堆内存泄露:
//		堆内存指的是程序执行中依据需要分配通过 malloc/calloc/new 等从堆中分配一块
// 内存,用完后必须通过调用相应的free或者delete 删掉。 假设程序的设计错误导致这部
// 分内存没有被释放,那么以后这部分内存将无法再次被使用,会产生 Heap Leak。

//  系统资源泄露:
//  指程序使用系统分配的资源,比如套接字,文件描述符,管道等没有使用对应的函数释放掉
// 导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。


// 避免内存泄漏的办法:
// 1. 采用RALL 思想或者智能指针来管理资源,(由对象的生命周期来管理内存!);

// RALL(resource acquisition is initialization) 是一种利用对象生命周期来控制程序资源的简单技术。
# if 0
	在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。
	借此,我们实际上把管理一份资源的责任交给了一个对象。
好处:
	* 不需要显示的释放资源。
	* 采用这种方式,对象所需的资源在其生命周期内始终保持有效。
#endif 

#if 0
// 采用 RALL 的思想设计SmartPtr 类

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

		~SmartPtr() {
			if (_ptr) {
				delete _ptr;
			}
		}
	private:
		T* _ptr;
	};
#endif 

	
#if 0

	智能指针的原理,SmartPtr 还不能将其称为只能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过-> 去访问空间上的内容
	因此: 要重载一线 *, -> 才能像指针一样使用。
#endif 

#if 0
#include <iostream> 

	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;
	};
	
	struct Date {
		int _year;
		int _month;
		int _day;
	};

	int main() {
		SmartPtr<int> sp1(new int);
		*sp1 = 10;
		std::cout << *sp1 << std::endl;

		SmartPtr<Date> sparr(new Date);

		sparr->_day = 10;
		sparr->_month = 7;
		sparr->_year = 2022;
		std::cout << " ";
		return 0;
	}
#endif 


#if 0
// C++ 98 提供 auto_ptr 的智能指针。(不好用,被拷贝的因该是调用了swap)
#include <iostream> 

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

	int main() {
		std::auto_ptr<Dat> ap(new Dat);
		std::auto_ptr<Dat> copy(ap);

		// auto_ptr 的问题,当对象拷贝或者赋值后,前面的对象就悬空了.
		// C++ 98 中auto_ptr 问题非常明显.
		copy->_year = 2023;
		ap->_year = 2022; // 悬空!== empty
		return 0;
	}

	// 模拟实现 C++98 中的Auto_ptr

	template<class T>
	class AutoPtr {
	public:
		AutoPtr(T* ptr):_ptr(ptr){}
		~AutoPtr() {
			if (_ptr) {
				delete _ptr;
			}
		}

		// 拷贝构造,一旦发生拷贝,就将ap中的资源转移到当前对象中,让后让ao与其管理的资源分手,这样就解决了一块内存被多个对象使用造成的程序崩溃问题!
		AutoPtr(AutoPtr<T>& ap):_ptr(ap) {
			ap._ptr = nullptr;
		}

		AutoPtr<T>& opeartor = (AutoPtr<T>&ap){
			// 防止递归赋值!
			if (this != &ap) {
				// 释放当前对象中的资源!
				if (_ptr)
					delete _ptr;
				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}
		return *this;
		}

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

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

#endif 

#if 0

	C++ 11 做了改进,为了防止一份内存再多个对象中使用造成程序崩溃的问题,直接来一个unique_ptr;
	// unique_ptr 直接简单粗暴的不允许拷贝和赋值操作,保证一个人只有一个老婆,而且不能再换老婆了!。

	// 下面简单的模拟实现一下:

	template<class T>
	class UniquePtr {
	private:
		T* _ptr;
	public:
		UniquePtr(T* ptr) :_ptr(ptr){}
		~UniquePtr() {
			if (_ptr) delete _ptr;
		}
		T& operator*() {
			return *_ptr;
		}

		T* operator->() {
			return _ptr;
		}
	private:
		// C++ 98 防止拷贝的方式:只声明不实现 + private 
		UniquePtr(const UniquePtr<T>& uq);
		UniquePtr& operator=(UniquePtr<T> const&);

		// C++ 11 防止拷贝的方式 == delete 
		UniquePtr(const UniquePtr<T>& uq) = delete;
		UniquePtr& operator=(UniquePtr<T> const&) = delete;
	};
#endif 

#if 0
#include <iostream>

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

	// C++ 11 提供类支持拷贝且靠谱的shared_ptr 
	int main() {
		// shared_ptr 通过引用计数支持智能指针对象的拷贝!
		std::shared_ptr<Dat> sp(new Dat);
		std::shared_ptr<Dat> copy(sp);

		std::cout << "ref count : " << sp.use_count() << std::endl;
		std::cout << "ref count : " << copy.use_count() << std::endl;
		return 0;
	}

	1. shared_ptr 在其内部,给每个资源都维护这一份计数器,用来记录该份资源被几个对象共享了。
	2. 在对象被销毁时(调用析构函数),就说明自己不使用该资源了,对象引用计数减一。
	3. 如果引用计数是 0 ,就说明自己是最后一个使用该资源的对象,必须释放该资源。
	4. 如果引用计数不是0,就说明出了自己还有其他对象在使用改分资源,不能释放该资源。

#endif 

#if 0
// 模拟实现一份 SharedPtr
#include <iostream> 
#include <mutex> 

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

		~SharedPtr() {
			Release();
		}

		SharedPtr(const SharedPtr<T>& sp) :_ptr(ptr), _pRefCount(new int(1)), _pMutex(new mutex) {
			AddRefCount();
		}

		SharedPtr<T>& operator=(const SharedPtr<T>& sp) {
			if (this != &sp) {
				// 释放管理的旧资源!
				Release();

				// 共享管理新对象的资源.
				_ptr = sp._ptr;
				_pRefCount = sp.__pRefCount;
				_pMutex = sp._pMutex;

				AddRefCount();
			}
			return *this;
		}

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

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

		int UseCount(){ return *_pRefCount; }
		T* Get() { return _ptr; }

		void AddRefCount() {
			_pMutex->lock();
			++(*pRefCount);
			_pMutex->unlock();
		}
	private:
		void Release() {
			bool = deleteflag = false;

			// 引用计数 - 1
			_pMutex->lock();
			if (--(*pRefCount) == 0) {
				delete _ptr;
				delete _pRefCount;
				deleteflag = true;
			}
			_pMutex->unlock();
			if (deleteflag) {
				delete _pMutex;
			}
		}
	private:
		int* _pRefCount; // 引用计数.
		T* _ptr; // 指向管理资源的指针.
		mutex* _pMutex; // 互斥锁
	};

#endif

#if 0
	智能指针(shared_ptr)的线程安全问题:
		1.shared_ptr 对象通过引用计数来实现多个智能指针对象共享,所以引用计数的过程一定要是原子的。
		2.shared_ptr 管理的对象存放在堆上,两个线程中同时去访问,会造线程安全问题!

		智能指针(shared_ptr)的循环引用问题:
#include <iostream> 

	using namespace std;
	struct ListNode {
		int _data;
		shared_ptr<ListNode> _prev;
		shared_ptr<ListNode> _next;
		~ListNode() { cout << " ~ListNode" << endl; }
	};

	int main() {
		shared_ptr<ListNode> node1(new ListNode);
		shared_ptr<ListNode> node2(new ListNode);

		cout << node1.use_count() << endl;
		cout << node2.use_count() << endl;

		node1->_next = node2;
		node2->_prev = node1;
		cout << node1.use_count() << endl;
		cout << node2.use_count() << endl;
		return 0;
	}

	1. node1 和 node2 两个智能指针对象指向两个点,引用计数变成1,我们不需要手动delete
    2. node1 的 _next 指向node2 ,node2 的_prev 指向 node1,引用计数变成2.
	3. node1和node2 析构,引用计数减到1,但是_next 还指向下一个节点._prev还指向上一个节点。
	   也就是说_next析构了node2就释放了。
	   也就是说_prev 析构了 node1 就释放了。
	4. 但是_next 属于node成员变量,node1释放了_next才会析构,而node1由_prev管理,_prev属于node2成员,
	   所以说真就叫循环引用.
	
	注意: 解决循环引用的方案,在引用计数的场景下,将_prev,_next, 改成weak_ptr就行了!
		// 原理上就是,node1->_next = node2; 和 node2 -> _prev = node1 ; 是weak_ptr 的_next 和 _prev 不会增加node1 和node2的引用计数。

#endif 

#if 0
		如果不是new 出来的对象如何通过智能指针管理呢? 其实shared_ptr 设计了一个删除器来解决问题!
		// 仿函数的删除器
	template <class T>
	struct FreeFunc {
		void operator()(T* ptr) {
			cout << "free : " << ptr << endl;
			free(ptr);
		}
	};

	template <class T>
	struct DeleteArrayFunc {
		void operator()(T* ptr) {
			cout << "free : " << ptr << endl;
			delete[] ptr;
		}
	};

	int main() {
		FreeFunc<int> freeFunc;
		shared_ptr<int> sp1((int*)malloc(4), freeFunc);

		DeleteArrayFunc<int> deleteArrayFunc;
		shared_ptr<int> sp2((int*)malloc(4), deleteArrayRunc);
		return 0;

	}

#endif 

#if 0
	C++11 和 boost中智能指针的关系。
		1. C++ 98 中产生了第一个智能指针 auto_ptr;
	2. C++ boost 给出了更实用的scoped_ptr和 shared_ptr 和 weak_ptr;
	3. C++11 TR1 ,引入了shared_ptr等。不过注意的是TR1并不是标准版的.
	4. C++11 引入了unique_ptr和shared_ptr 和 weak_ptr. 需要注意的是unique_ptr 对应boost的scoped_ptr.
		这些智能指针的实现原理都是参考boost中实现的.
#endif 


#if 0
		RALL 的思除了设计只能指针,还可以用来设计守卫锁,防止异常安全导致死锁问题!

#include <thread>
#include <mutex>
		// C++ 11 库中有一个 lock_guard 下面模仿一下!
	template<class Mutex>
	class LockGuard {
		LockGuard(Mutex& mtx):_mutex(mtx) {
			_mutex.lock();
		}
		~LockGuard() {
			_mutex.unlock();
		}
	private:
		Mutex& _mutex; // 注意这里必须使用引用,否则搜就不是一个互斥量对象。
	};
	mutex mtx;
	static int n = 0;
	void Func() {
		for (size_t i = 0; i < 10000; i++) {
			LockGuard<mutex> lock(mtx);
			++n;
		}
	}

	int main() {
		int begin = clock();

		thread t1(Func);
		thread t2(Func);

		t1.join();
		t2.join();

		int end = clock();

		count << n << endl;
		count << "cost time: " << end - begin << endl;
		return 0;
	}
#endif 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值