智能指针ok的

智能指针

模拟实现智能指针
概念
智能指针通常用于对一个资源进行管理,避免因为疏忽或错误造成程序未能释放已经不再使用的内存的情况

智能指针的设计
1、在对象构造时获取资源,在对象析构时释放资源,利用对象的生命周期来控制资源,即RAII特性(利用RALL思想设计delete资源的类)
2、对*和->运算符进行重载,使得智能指针具有指针一样的行为 (像指针一样的行为)
3、智能指针的拷贝问题(根据解决拷贝问题方式的不同,从而衍生出了不同版本的智能指针)

1.auto_ptr
思路是管理权转移,缺点:没有管理权的智能指针不能使用,否则空指针访问崩溃

namespace cl
{
	template<class T>
	class auto_ptr
	{
	public:
		//RAII
		auto_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{}
		~auto_ptr()
		{
			if (_ptr != nullptr)
			{
				cout << "delete: " << _ptr << endl;
				delete _ptr;
				_ptr = nullptr;
			}
		}
		auto_ptr(auto_ptr<T>& ap)
			:_ptr(ap._ptr)
		{
			ap._ptr = nullptr; //管理权转移后ap被置空
		}
		auto_ptr& operator=(auto_ptr<T>& ap)
		{
			if (this != &ap)
			{
				delete _ptr;       //释放自己管理的资源
				_ptr = ap._ptr;    //接管ap对象的资源
				ap._ptr = nullptr; //管理权转移后ap被置空
			}
			return *this;
		}
		//可以像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr; //管理的资源
	};
}

2.unique_ptr
思路是防拷贝,缺点:无法对unique_ptr对象进行拷贝
c++11实现:禁止拷贝构造和赋值重载
c++98实现:拷贝构造和赋值重载只声明不实现并且设置private

namespace cl
{
	template<class T>
	class unique_ptr
	{
	public:
		//RAII
		unique_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{}
		~unique_ptr()
		{
			if (_ptr != nullptr)
			{
				cout << "delete: " << _ptr << endl;
				delete _ptr;
				_ptr = nullptr;
			}
		}
		//可以像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		//防拷贝
		unique_ptr(unique_ptr<T>& up) = delete;
		unique_ptr& operator=(unique_ptr<T>& up) = delete;
	private:
		T* _ptr; //管理的资源
	};
}

3.shared_ptr

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

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

		void Release()
		{
			_pmtx->lock();

			bool flag = false;
			if (--(*_pRefCount) == 0 && _ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
				delete _pRefCount;

				flag = true;
			}

			_pmtx->unlock();

			if (flag == true)
			{
				delete _pmtx;
			}
		}

		void AddRef()
		{
			_pmtx->lock();

			++(*_pRefCount);

			_pmtx->unlock();
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//if (this != &sp)
			if (_ptr != sp._ptr)
			{
				Release();

				_ptr = sp._ptr;
				_pRefCount = sp._pRefCount;
				_pmtx = sp._pmtx;
				AddRef();
			}

			return *this;
		}

		int use_count()
		{
			return *_pRefCount;
		}

		~shared_ptr()
		{
			Release();
		}

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

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

		T* get() const
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pRefCount;
		mutex* _pmtx;
	};


4.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;
	};
}

5. 定制删除器

  • // 定制删除器 考试基本不考,实际使用有价值
  • // 默认情况,智能指针底层都是delete资源
  • // 那么如果你的资源不是new出来的呢?比如:new[]、malloc、fopen // 定制删除器 – 可调用对象

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

struct DeleteFile
{
	void operator()(FILE* ptr)
	{
		cout << "fclose:" << ptr << endl;
		fclose(ptr);
	}
};

	// 释放方式有D删除器决定
	template<class T, class D = default_delete<T>>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

		~unique_ptr()
		{
			if (_ptr)
			{
				//cout << "delete:" << _ptr << endl;
				//delete _ptr;
				D del;
				del(_ptr);
			}
		}

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

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

		unique_ptr(const unique_ptr<T>& sp) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;

	private:
		T* _ptr;
	};


int main()
{
	// 删除器在类模板参数给 -- 类型
	yyk::unique_ptr<A> up1(new A);
	yyk::unique_ptr<A, DeleteArray<A>> up2(new A[10]);
	yyk::unique_ptr<FILE, DeleteFile> up3(fopen("test.txt", "w"));

	// 删除器在构造函数的参数给 -- 对象
	std::shared_ptr<A> sp1(new A);
	std::shared_ptr<A> sp2(new A[10], DeleteArray<A>());
	std::shared_ptr<FILE> sp3(fopen("test.txt", "w"), DeleteFile());

	std::shared_ptr<A> sp4(new A[10], [](A* p){delete[] p; });
	std::shared_ptr<FILE> sp5(fopen("test.txt", "w"), [](FILE* p){fclose(p); });

	return 0;
}

补充

shared_ptr

1.智能指针使用场景案例

    1. 使用智能指针可以自动释放占用的内存.
    1. 共享所有权指针的传播和释放.

在这里插入图片描述

2. shared_ptr内存模型

在这里插入图片描述

shared_ptr 内部包含两个指针,一个指向对象,另一个指向控制块(control block),控制块中包含一个
引用计数(reference count), 一个弱计数(weak count)和其它一些数据。

在这里插入图片描述

3 shared_ptr共享的智能指针

单来说**,shared_ptr**实现包含了两部分,

  • 一个指向堆上创建的对象的裸指针,raw_ptr
  • 一个指向内部隐藏的、共享的管理对象。share_count_object
    第一部分没什么好说的,第二部分是需要关注的重点:
  • use_count,当前这个堆上对象被多少对象引用了,简单来说就是引用计数

4. shared_ptr的基本使用

s.get():返回shared_ptr中保存的裸指针;

s.reset(p,…):重置shared_ptr;

  • reset( )不带参数时,若智能指针s是唯一指向该对象的指针,则释放,并置空。若智能指针P不是唯
    一指向该对象的指针,则引用计数减少1,同时将P置空。
  • reset( )带参数时,若智能指针s是唯一指向对象的指针,则释放并指向新的对象。若P不是唯一的指
    针,则只减少引用计数,并指向新的对象。如:
#include <iostream>
#include <memory>

int main () {
  std::shared_ptr<int> sp;  // empty

  sp.reset (new int);       // takes ownership of pointer
  *sp=10;
  std::cout << *sp << '\n';

  sp.reset (new int);       // deletes managed object, acquires new pointer
  *sp=20;
  std::cout << *sp << '\n';

  sp.reset();               // deletes managed object

  return 0;
}

s.use_count() :返回shared_ptr的强引用计数;
s.unique() :若use_count()为1,返回true,否则返回false。

4.1 初始化make_shared/reset
通过构造函数、std::shared_ptr辅助函数和reset方法来初始化shared_ptr,代码如下:

std::shared_ptr<int> p1(new int(1));
std::shared_ptr<int> p2 = p1;
std::shared_ptr<int> p3;
p3.reset(new int(1));
if(p3) {
cout << "p3 is not null";
}

4.2 我们应该优先使用make_shared来构造智能指针,因为他更高效。

auto sp1 = make_shared<int>(100);
或者
shared_ptr<int> sp1 = make_shared<int>(100);
//相当于
shared_ptr<int> sp1(new int(100));

4.3 错误使用:

  • 不能将一个原始指针直接赋值给一个智能指针,例如,下面这种方法是错误的
std::shared_ptr<int> p = new int(1);

4.4 shared_ptr不能通过“直接将原始这种赋值”来初始化,需要通过构造函数和辅助方法来初始化.

  • 对于一个未初始化的智能指针,可以通过reset方法来初始化;
  • 当智能指针有值的时候调用reset会引起引用计数减1。

4.5 另外智能指针可以通过重载的bool类型操作符来判断。

#include <iostream>
#include <memory>
using namespace std;
int main()
{
    std::shared_ptr<int> p1;
    if(!p1)
    {
        cout<<"p1 未初始化\n";  // 被打印
    }
    p1.reset();
    if(!p1)
    {
        cout<<"p1.reset()\n";// 被打印
    }
    p1.reset(new int(1));
    if(!p1)
    {
        cout<<"p1.reset(new int(1));\n";
    }
    return 0;
}

4.6 获取原始指针get

当需要获取原始指针时,可以通过get方法来返回原始指针,代码如下所示:

std::shared_ptr<int> ptr(new int(1));
int *p = ptr.get(); 
//不小心 delete p;

谨慎使用p.get()的返回值,如果你不知道其危险性则永远不要调用get()函数。
p.get()的返回值就相当于一个裸指针的值,不合适的使用这个值,上述陷阱的所有错误都有可能发生,
遵守以下几个约定:

  • 不要保存p.get()的返回值 ,无论是保存为裸指针还是shared_ptr都是错误的
  • 保存为裸指针不知什么时候就会变成空悬指针,保存为shared_ptr则产生了独立指针
  • 不要delete p.get()的返回值会导致对一块内存delete两次的错误

4.7 指定删除器

如果用shared_ptr管理非new对象或是没有析构函数的类时,应当为其传递合适的删除器 。

示例代码如下:

//1-3-1-delete
#include <iostream>
#include <memory>
using namespace std;

void DeleteIntPtr(int *p) {
	cout << "call DeleteIntPtr" << endl;
	delete p;
} 

int main()
{
	std::shared_ptr<int> p(new int(1), DeleteIntPtr);
}

当p的引用计数为0时,自动调用删除器DeleteIntPtr来释放对象的内存。删除器可以是一个lambda表达
式,上面的写法可以改为:

std::shared_ptr<int> p(new int(1), [](int *p) {
cout << "call lambda delete p" << endl;
delete p;});

当我们用shared_ptr管理动态数组时,需要指定删除器,因为shared_ptr的默认删除器不支持数组对
象,代码如下所示:

std::shared_ptr<int> p3(new int[10], [](int *p) { delete [] p;});

std::shared_ptr p3(new int[10], [](int *p) { delete [] p;});

4.8 使用shared_ptr要注意的问题

1.不要用一个原始指针初始化多个shared_ptr。

例如下面错误范例:

能看懂模拟实现就明白了。

int *ptr = new int;
shared_ptr<int> p1(ptr);
shared_ptr<int> p2(ptr); // 逻辑错误

2.不要在函数实参中创建shared_ptr

对于下面的写法:

function(shared_ptr<int>(new int), g()); //有缺陷

因为C++的函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的,一般是从右到左,但也
可能从左到右,所以,可能的过程是先new int,然后调用g(),如果恰好g()发生异常,而shared_ptr还
没有创建, 则int内存泄漏了,正确的写法应该是先创建智能指针,代码如下:

shared_ptr<int> p(new int);
function(p, g());

3.通过shared_from_this()返回this指针

不要将this指针作为shared_ptr返回出来,因为this指针本质上是一个裸指针,因此,这样可能会导致
重复析构,看下面的例子。

//1-3-2-shared_from_this
#include <iostream>
#include <memory>
using namespace std;
class A
{ 
public:
	shared_ptr<A> GetSelf()
	{
		return shared_ptr<A>(this); // 不要这么做
	} 
	~A()
	{
		cout << "Destructor A" << endl;
	}
};
int main()
{
	shared_ptr<A> sp1(new A);
	shared_ptr<A> sp2 = sp1->GetSelf();
	return 0;

在这里插入图片描述
在这个例子中,由于用同一个指针(this)构造了两个智能指针sp1和sp2,而他们之间是没有任何关系
的,在离开作用域之后this将会被构造的两个智能指针各自析构,导致重复析构的错误。
正确返回this的shared_ptr的做法是:让目标类通过std::enable_shared_from_this类,然后使用基类的
成员函数shared_from_this()来返回this的shared_ptr,如下所示。

#include <memory>
using namespace std;

class A: public std::enable_shared_from_this<A>
{ 
public:
	shared_ptr<A>GetSelf()
	{
		return shared_from_this(); // sp1的引用计数++;
	} 
	~A()
	{
		cout << "Destructor A" << endl;
	}

};
int main()
{
	shared_ptr<A> sp1(new A);
	shared_ptr<A> sp2 = sp1->GetSelf(); // ok

    cout<< sp1.get()<<endl; // 0xdaec20
    cout<< sp2.get()<<endl; // 0xdaec20

	return 0;
}

在weak_ptr章节我们继续讲解使用shared_from_this()的原因。

4. 避免循环引用
当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。

struct ListNode
{
	int _data;
	hared_ptr<ListNode> _prev;
	shared_ptr<ListNode> _next;
	
	~ListNode(){ 
		cout << "~ListNode()" << endl; }
	};
void test()
{
	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;
}
// 解决方案:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了
// 原理就是,node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和
_prev不会增加node1和node2的引用计数。
struct ListNode
{
	int _data;
	weak_ptr<ListNode> _prev;
	weak_ptr<ListNode> _next;
	~ListNode(){ cout << "~ListNode()" << endl; }
};

4.9 智能指针实践

比如直播场景,一个主播对应多个观众,此时在流媒体服务器中需要将主播的音视频数据帧转发给上千
个观众,我们该如何设计数据模型.

unique_ptr 的基本使用

  • unique_ptr没有默认的删除器,需要我们手动写 。
  • unique_ptr 虽然是独占型的智能指针,对拷贝,构造函数禁止 ,但是移动构造,移动赋值并没有禁止,因此可能会通过move函数让左值拷贝。

weak_ptr弱引用的基本使用

一般被称为弱智能指针,其对资源的引用不会引起资源的引用计数的变化**,通常作为观察者,用于判断资源是否存在**,并根据不同情况做出相应的操作。

1. 通过use_count()方法获取当前观察资源的引用计数,如下所示:

shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp); // weak_ptr 并不会影响sp的引用计数
cout << wp.use_count() << endl; //结果讲输出1

2. 通过expired()方法判断所观察资源是否已经释放,如下所示:
如下代码只是作为演示,sp的资源在某下场景下可能已经释放了,因此如果不对weak_ptr做检查,有可能野指针问题.

shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
if(wp.expired())
	cout << "weak_ptr无效,资源已释放"; 
else
	cout << "weak_ptr有效";// 被打印

3. 通过lock方法获取监视的shared_ptr,如下所示:

lock有什么用处?
让引用计数++,并放回shared_ptr对象.

线程1里,执行gw.lock 前,有可能sp的资源已经释放了,那么gw也就是空指针了.

线程1里执行gw.lock 前, sp的资源没有释放, 那么执行gw.lock会执行sp资源计数器++,延长了声明周期.

gw.expiced 不管如何,只要前面有 lock( ),那么必须检查weak_ptr 对象是否存在.
在这里插入图片描述

锁定资源有可能资源已经释放,所以我们不能保证资源是否还在,需要使用expired()函数进行检测.

4. weak_ptr返回this指针

shared_ptr章节中提到不能直接将this指针返回shared_ptr,
需要通过继承std::enable_shared_from_this类并通过其方法shared_from_this来返回指针,原因是
std::enable_shared_from_this类中有一个weak_ptr,这个weak_ptr用来观察this智能指针,调用
shared_from_this()方法是,会调用内部这个weak_ptr的lock()方法,将所观察的shared_ptr返回,再看
前面的范例

//1-3-2-shared_from_this
#include <iostream>
#include <memory>
using namespace std;
class A
{ 
public:
	shared_ptr<A> GetSelf()
	{
		return shared_ptr<A>(this); // 不要这么做
	} 
	~A()
	{
		cout << "Destructor A" << endl;
	}
};
int main()
{
 	// 析构两次 ~A();
	shared_ptr<A> sp1(new A);
	shared_ptr<A> sp2 = sp1->GetSelf();
	return 0;
}

需要注意的是,获取自身智能指针的函数尽在shared_ptr的构造函数被调用之后才能使用,因为
enable_shared_from_this内部的weak_ptr只有通过shared_ptr才能构造。

使用enable_shared_from_this:


class A: public std::enable_shared_from_this<A>
{ 
public:
	shared_ptr<A>GetSelf()
	{
		return shared_from_this(); // sp1的引用计数++;
	} 
	~A()
	{
		cout << "Destructor A" << endl;
	}

};

int main()
{
 	// 析构两次 ~A();
	shared_ptr<A> sp1(new A);
	shared_ptr<A> sp2 = sp1->GetSelf();
	return 0;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

2023框框

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

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

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

打赏作者

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

抵扣说明:

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

余额充值