【C++智能指针】智能指针的发展和循环引用的原理和解决

目录

1.RAIl(智能指针的雏形)

2.拷贝导致的问题以及智能指针发展历史

2.1拷贝的问题(资源被析构两次)

2.2auto_ptr(资源权转移,不建议使用)

         2.3unique_ptr(防拷贝,在不需要拷贝的情况下使用) 

         2.4shared_ptr

2.4.2循环引用的问题(重点)

 3.定制删除器


智能指针是拿来处理异常安全问题的

  • 下面这段代码如果抛异常了,那么p1就指向的空间就没有被释放,内存泄漏 
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");

	return a / b;
}

void func()
{
	int* p1 = new int;
	cout << div() << endl; // 异常安全的问题

	delete p1;
}

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

1.RAIl(智能指针的雏形)

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源,对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。(就是把原本资源托管给一个类对象)

优势:

  1. 不需要显式地释放资源。
  2. 采用这种方式,对象所需的资源在其生命期内始终保持有效(声明周期在调用栈帧里面,声明周期结束自动调析构函数)
template
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}
    //生命周期结束,自动析构
	~SmartPtr()
	{
		cout << "delete:" << _ptr << endl;
		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 sp1(new int);
	SmartPtr sp2(new int);
	SmartPtr sp3(new int);

	*sp1 = 10;
	cout << *sp1 << endl;
	(*sp1)++;
	(*sp1)++;
	cout << *sp1 << endl;

	cout << div() << endl;
}

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

	return 0;
}

 执行结果·:【C++智能指针】智能指针的发展和循环引用的原理和解决_第1张图片

解决了如果抛异常,在堆上开的空间得不到释放,导致的内存问题;

重载operator*和opertaor->,具有像指针一样的行为,可以正常访问

2.拷贝导致的问题以及智能指针发展历史

2.1拷贝的问题(资源被析构两次)

// RAII,把资源托管给一个类对象
// 用起来像指针一样
template
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}

	~SmartPtr()
	{
		cout << "delete:" << _ptr << endl;
		delete _ptr;
	}

	
	T& operator*()
	{
		return *_ptr;
	}
	// 像指针一样使用
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};

int main()
{
	//SmartPtr sp1(new int);
	//SmartPtr sp2(sp1);

	SmartPtr sp1(new int);
	SmartPtr sp2(new int);
	sp2 = sp1;

	return 0;
}

执行结果【C++智能指针】智能指针的发展和循环引用的原理和解决_第2张图片

  •  默认生成的拷贝构造,赋值重载都是浅拷贝,同一块空间被析构两次,程序奔溃

不能深拷贝的原因 【C++智能指针】智能指针的发展和循环引用的原理和解决_第3张图片

 2.2auto_ptr(资源权转移,不建议使用)

//auto_ptr
namespace LDJ
{
	template
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}

		auto_ptr(auto_ptr& sp)
			:_ptr(sp._ptr)
			//:_ptr(nullptr)
		{
			// 管理权转移
			sp._ptr = nullptr;
			//swap(_ptr, sp._ptr);
		}

		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}

		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}

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

// 结论:auto_ptr是一个失败设计
int main()
{
	LDJ::auto_ptr sp1(new int);
	LDJ::auto_ptr sp2(sp1); // 管理权转移
	// sp1悬空
	*sp2 = 10;
	cout << *sp1 << endl;
	cout << *sp2 << endl;

	return 0;
}

执行结果【C++智能指针】智能指针的发展和循环引用的原理和解决_第4张图片

  • sp1悬空了; 

2.3unique_ptr(防拷贝,在不需要拷贝的情况下使用) 

  • unique_ptr的实现原理:简单粗暴的防拷贝
namespace LDJ
{
	template>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;

			}
		}

		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}

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

		unique_ptr(const unique_ptr& sp) = delete;
		unique_ptr& operator=(const unique_ptr& sp) = delete;

	private:
		T* _ptr;
	};
}

2.4shared_ptr

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减 一。
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了
namespace LDJ
{
	template
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pRefCount(new int(1))
		{}
		void AddRef()
		{
			 ++*(_pRefCount);
		}
		void Release()
		{

			if (--(*_pRefCount) == 0 && _ptr)
			{
				delete _ptr;
				delete _pRefCount;

			}
		}
		shared_ptr(const shared_ptr& sp)
			:_ptr(sp._ptr)
			, _pRefCount(sp._pRefCount)
			, _pmtx(sp._pmtx)
		{
			AddRef();
		}

		~shared_ptr()
		{
			Release();
		}

		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pRefCount;
	}
}

【C++智能指针】智能指针的发展和循环引用的原理和解决_第5张图片2.4.1operator=需要处理一下

shared_ptr& operator=(const shared_ptr& sp)
		{
			//if (this != &sp)
			if (this->_pRefCount != sp._pRefCount)
			{
				Release();
				_ptr = sp._ptr;
				_pRefCount = sp._pRefCount;
				AddRef();
				return *this;
			}
		}

2.4.2循环引用的问题(重点)

struct Node
{
	~Node()
	{
		cout << "~Node" << endl;
	}
	int val;
	std::shared_ptr _next;
	std::shared_ptr _prev;
};
int main()
{
	std::shared_ptr sp1(new Node);
	std::shared_ptr sp2(new Node);
	sp1->_next = sp2;
	sp2->_prev = sp1;

	return 0;
}

执行结果:如果被析构了会打印两行~Node,然而并没有,说明sp1和sp2都没有被释放

【C++智能指针】智能指针的发展和循环引用的原理和解决_第6张图片

 

循环引用的具体原因: 

【C++智能指针】智能指针的发展和循环引用的原理和解决_第7张图片

解决循环引用weak_ptr

  • weak_ptr不是常规的智能指针,不增加计数支持访问,所以也不支持RAII 

【C++智能指针】智能指针的发展和循环引用的原理和解决_第8张图片

struct Node
{
	~Node()
	{
		cout << "~Node" << endl;
	}
	int val;
	std::weak_ptr _next;
	std::weak_ptr _prev;
};
int main()
{
	std::shared_ptr sp1(new Node);
	std::shared_ptr sp2(new Node);
	cout << "sp1Count:" << sp1.use_count() << endl;
	cout << "sp2Count:" << sp2.use_count() << endl;
	sp1->_next = sp2;
	sp2->_prev = sp1;
	cout << "sp1Count:" << sp1.use_count() << endl;
	cout << "sp2Count:" << sp2.use_count() << endl;
	return 0;
}

 执行结果:前后sp1和sp2的计数都为1;所以可以证明weak_ptr不增加引用计数且解决了循环引用的问题;

【C++智能指针】智能指针的发展和循环引用的原理和解决_第9张图片

 3.定制删除器

那么如果资源不是new出来的呢?比如:new[]、malloc、fopen,delete就不够用了

template
struct DeleteArray
{
	void operator()(const T* ptr)
	{
		delete[] ptr;
	}
};
struct DeleteFile
{
	void operator()(FILE* ptr)
	{
		fclose(ptr);
	}
};
    unique_ptr up1(new A);
    unique_ptr> up2(new A[10]);
    unique_ptr up3(fopen("test.txt", "w"));

你可能感兴趣的:(c++,c++,算法,开发语言)