C++小工进阶之路XXV(智能指针下)

继承的总结和反思
	1. 很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有
	菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在
	复杂度及性能上都有问题。
	2. 多继承可以认为是C++的缺陷之一,很多后来的OO语言都没有多继承,如Java。
	3. 继承和组合
public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
优先使用对象组合,而不是类继承 。
继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用
(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。
继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关
系很强,耦合度高。
对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对
象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),
因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关系,
耦合度低。优先使用对象组合有助于你保持每个类被封装。
实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适
合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就
用组合。

public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。(继承)
组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。(类中使另一个类的对象,做成员变量)

如果单纯是考虑代码的复用,可以考虑组合而不是继承,而要使实现多态的话优先考虑的使继承

class Base1 {
public:
	int _b1;
};
class Base2 {
public:
	int _b2;
};
class Derive : public Base1, public Base2 {
public:
	int _d;
};
int main(){
	// A. p1 == p2 == p3	
	// B. p1 < p2 < p3
	// C. p1 == p3 != p2
	// D. p1 != p2 != p3
	Derive d;
	Base1* p1 = &d;
	Base2* p2 = &d;
	Derive* p3 = &d;
	//选c
	return 0;
}

在这里插入图片描述

============================================================================

智能指针:
1.为什么需要智能指针?
资源泄露(内存泄漏)
2.什么是RAII?利用析构函数和构造函数来管理资源的思想
3.各个智能指针的实现原理,以及存在的问题
RAII封装资源----》用户不要考虑什么时候释放资源,将资源泄漏的概率降低
operator*()/operator->()--------》智能指针的对象可以像指针的方式使用
4.所有的不同的智能指针都是在解决浅拷贝的问题
不同的智能指针解决浅拷贝的问题的方式不相同

auto_ptr:C++98中的使用的是资源的转移,来实现解决浅拷贝问题,
缺陷:
auto_ptr< int> ap2(ap1);
ap1,和ap2不能和同时访问资源
之后又提出了一个改进的版本,那就是在类中加上一个变量_owner
优点是:可以共享资源,缺陷:可能会造成野指针(当两个智能指针所处的作用域不同时,当拥有资源管理权限并且作用域小的指针在离开作用域释放资源之后,外部的指针就会变成野指针,危害还是比较严重的)

C++11,又给出来两个智能指针的版本
unique_ptr: 资源独占(对象之间不能共享资源),切中引起浅拷贝问题的痛点,就是在用户不提供拷贝构造和赋值运算符重载的时候,会调用编译器默认的函数,引起浅拷贝,所以在unique_ptr中直接就是禁止调用编译器默认生成的构造函数和赋值运算符重载。简单粗暴,但是应用场景也受限了,无法共享资源

**shared_ptr:**采用引用计数的方式来解决浅拷贝---->
优势:多个对象之间可以共享资源
缺陷:可能会存在循环引用,导致资源泄漏
记录资源被多少个对象在使用

实现简单的shared_ptr

#include<iostream>
using namespace std;
namespace vs
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, count(nullptr)  //如果刚开始资源指向空,就让资源计数也为空
		{
			if (_ptr)
				count =new int(1);
		}
		~shared_ptr()
		{
			if (_ptr && --(*count) == 0)
			{
				delete _ptr;
				_ptr = nullptr;
				delete count;
				count = nullptr;
			}
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		shared_ptr(shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, count(sp.count)
		{
			if (_ptr)
				++(*count);
		}
		shared_ptr<T>& operator=(shared_ptr<T>& sp)
		{
			if (this != &sp)
			{
				if (_ptr && --(*count) == 0)
				{
					delete sp._ptr;
					delete sp.count;
				}
				_ptr = sp._ptr;
				count = sp.count;
				if (_ptr)
					++(*count);
			}
			return *this;
		}
		int use_count()
		{
			return *count;
		}
	private:
		T* _ptr;
		int *count;
	};
}
void test()
{
	vs::shared_ptr<int> p1 = new int(10);
	cout << p1.use_count() << endl;
	vs::shared_ptr<int> p2(p1);
	cout << p2.use_count() << endl;
	vs::shared_ptr<int> p3;
	p3 = p1;
	cout << p3.use_count() << endl;

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

现在上面的代码还存在两个问题,
1.就是释放空间只能通过delete 的方式释放,也就是说我们的代码只能管理new出来的空间
解决办法就是定制删除器:让用户可以控制资源具体的释放操作

通过仿函数

using namespace std;
template<class T>
class DFDef
{
public:
	void operator()(T*& p)
	{
		if (p)
		{
			delete p;
			p = nullptr;
		}
	}
};
template<class T>
class Free
{
public:
	void operator()(T* &p)
	{
		if (p)
		{
			free(p);
			p = nullptr;
		}
	}
};
class FClose
{
public:
	void operator()(FILE*& p)
	{
		if (p)
		{
			fclose(p);
		}
	}
};
namespace vs
{
	template<class T,class DF = DFDef>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, count(nullptr)  //如果刚开始资源指向空,就让资源计数也为空
		{
			if (_ptr)
				count =new int(1);
		}
		~shared_ptr()
		{
			if (_ptr && --(*count) == 0)
			{
				DE df;
				df(_ptr);
				df(count);
			}
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		shared_ptr(shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, count(sp.count)
		{
			if (_ptr)
				++(*count);
		}
		shared_ptr<T>& operator=(shared_ptr<T>& sp)
		{
			if (this != &sp)
			{
				if (_ptr && --(*count) == 0)
				{
					delete sp._ptr;
					delete sp.count;
				}
				_ptr = sp._ptr;
				count = sp.count;
				if (_ptr)
					++(*count);
			}
			return *this;
		}
		int use_count()
		{
			return *count;
		}
	private:
		T* _ptr;
		int *count;
	};
}
void test()
{
	vs::shared_ptr<int> up1(new int);
	vs::shared_ptr<int, Free<int>> up2((int*)malloc(sizeof(int)));
	vs::shared_ptr<FILE, FClose> up3(fopen("1.txt","w"));
}

2.线程不安全

============================================================================
关于shared_ptr循环引用导致资源泄漏的场景。(使用系统自带的shared_ptr)

#include<iostream>
#include<memory>
using namespace std;
struct ListNode
{
	ListNode(int data = 0)
	:_pre(nullptr)
	, _next(nullptr)
	, _data(data)
	{
		cout << "ListNode" << endl;
	}
	~ListNode()
	{
		cout << "~ListNode" << endl;
	}
	shared_ptr<ListNode> _pre;
	shared_ptr<ListNode> _next;
	int _data;
};
void Test()
{
	shared_ptr<ListNode> sp1(new ListNode(10));
	shared_ptr<ListNode> sp2(new ListNode(20));
	cout << sp1.use_count() << endl;
	cout << sp2.use_count() << endl;
	sp1->_next = sp2;  //这两句代码属于循环引用
	sp2->_pre = sp1;
	cout << sp1.use_count() << endl;
	cout << sp2.use_count() << endl;
}

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

打印结果:没有调用析构函数引起内存泄露
在这里插入图片描述
那可能存在循环引用引起的资源泄漏那么又该如何解决呢?
循环引用引起资源泄露的示意图:
在这里插入图片描述
解决方式:又提出了一个新的智能指针weak_ptr
使用weak_ptr来解决shared_ptr中存在的循环引用的问题
weak_ptr:实现原理和shared_ptr类似----引用计数

在这里插入图片描述
使用之后发现weak_ptr报错,也就是说weak_ptr不能独立的去管理资源
因为之前已经所过了,weak_ptr的出现只是为了解决sheard_ptr之中的缺陷,所以weak_ptr对象不能独立的管理资源,因为weak_ptr必须配合shared_ptr,来解决sheard_ptr之中所存在的问题。

在这里插入图片描述

#include<iostream>
#include<memory>
using namespace std;
struct ListNode
{
	ListNode(int data = 0)
	:_data(data)
	{
		cout << "ListNode" << endl;
	}
	~ListNode()
	{
		cout << "~ListNode" << endl;
	}
	weak_ptr<ListNode> _pre;
	weak_ptr<ListNode> _next;
	int _data;
};
void Test()
{
	shared_ptr<ListNode> sp1(new ListNode(10));
	shared_ptr<ListNode> sp2(new ListNode(20));
	cout << sp1.use_count() << endl;
	cout << sp2.use_count() << endl;
	sp1->_next = sp2;  //这两句代码属于循环引用
	sp2->_pre = sp1;
	cout << sp1.use_count() << endl;
	cout << sp2.use_count() << endl;
}
int main()
{
	Test();
	return 0;
}

结果:
在这里插入图片描述
在可能引起循环引用导致内存泄露的地方使用weak_ptr指针来管理,weak_ptr内部并不会使引用计数增加。

weak_ptr是如何解决循环引用问题的?

发布了230 篇原创文章 · 获赞 28 · 访问量 9331

猜你喜欢

转载自blog.csdn.net/weixin_43767691/article/details/103337687