【C++】智能指针

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

文章目录

前言

一、为什么需要智能指针?

二、内存泄漏

[2.1 什么是内存泄漏,内存泄漏的危害](#2.1 什么是内存泄漏,内存泄漏的危害)

[2.2 内存泄漏分类(了解)](#2.2 内存泄漏分类(了解))

[2.3 如何检测内存泄漏(了解)](#2.3 如何检测内存泄漏(了解))

2.4如何避免内存泄漏

三、智能指针的使用及原理

[3.1 RAII](#3.1 RAII)

[3.2 智能指针的原理](#3.2 智能指针的原理)

[3.3 std::auto_ptr](#3.3 std::auto_ptr)

[3.4 std::unique_ptr](#3.4 std::unique_ptr)

[3.5 std::shared_ptr](#3.5 std::shared_ptr)

[3.6 std::shared_ptr的循环引用](#3.6 std::shared_ptr的循环引用)

[3.7 采用std::weak_ptr来解决std::shared_ptr的循环引用问题](#3.7 采用std::weak_ptr来解决std::shared_ptr的循环引用问题)

[3.8 定制删除器](#3.8 定制删除器)

四、C++11和boost中智能指针的关系

总结



前言

世上有两种耀眼的光芒,一种是正在升起的太阳,一种是正在努力学习编程的你!一个爱学编程的人。各位看官,我衷心的希望这篇博客能对你们有所帮助,同时也希望各位看官能对我的文章给与点评,希望我们能够携手共同促进进步,在编程的道路上越走越远!


提示:以下是本篇文章正文内容,下面案例可供参考

一、为什么需要智能指针?

cpp 复制代码
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	// 1、如果p1这里new 抛异常会如何?
    // 2、如果p2这里new 抛异常会如何?
    // 3、如果div调用这里又会抛异常会如何?
	// p1这里的new抛异常,也就是这里没有调用构造函数初始化,自然p1就没有被构造出来,直接跳转到catch处就可以了
	int* p1 = new int;
	// p2这里的new抛异常,自然p2就没有构造出来,直接跳转到catch处,那么p1没有被detele,造成内存泄露
	int* p2 = new int;

	// div()函数抛异常,那么p1和p2对象没有被delete,造成内存泄露
	cout << div() << endl;

	delete p1;
	delete p2;
}
int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

二、内存泄漏

2.1 什么是内存泄漏,内存泄漏的危害

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内 存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对 该段内存的控制,因而造成了内存的浪费。

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现 内存泄漏会导致响应越来越慢,最终卡死。

cpp 复制代码
void MemoryLeaks()
{
	// 1.内存申请了忘记释放
	int* p1 = (int*)malloc(sizeof(int));
	int* p2 = new int;

	// 2.异常安全问题
	int* p3 = new int[10];

	Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.

	delete[] p3;
}

2.2 内存泄漏分类(了解)

C/C++程序中一般我们关心两种方面的内存泄漏:

  • 堆内存泄漏(Heap leak)

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

  • 系统资源泄漏

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

2.3 如何检测内存泄漏(了解)

2.4如何避免内存泄漏

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps: 这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智 能指针来管理才有保证。
  2. 采用RAII思想或者智能指针来管理资源。
  3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
  4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。

总结一下:

  • 内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄 漏检测工具。

三、智能指针的使用及原理

3.1 RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内 存、文件句柄、网络连接、互斥量等等)的简单技术。

在对象构造时获取资源, 接着控制对资源的访问使之在对象的生命周期内始终保持有效,**最后在 对象析构的时候释放资源。**借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做 法有两大好处:

  • 不需要显式地释放资源。
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效。

RAII的本质: 获取一个资源的时候,拿这个资源去初始化一个对象,在对象的生命周期之内,资源始终是有效的,也就是把资源交给一个对象去管理,而不是我们显示(delete/free...)去管理。

抛异常会影响执行流,但是函数的栈帧会正常结束。

vector/list....深拷贝:利用资源储存管理数据,资源是自己的,拷贝时,每个对象各自一份资源,各管各的,所以深拷贝。

智能指针/迭代器.....浅拷贝:本质资源不是自己的,代为持有,方便访问修改数据。它们拷贝的时候期望的指向同一个资源,所以浅拷贝。

3.2 智能指针的原理

cpp 复制代码
SmartPar.h
cpp 复制代码
template<class T>
class SmartPtr
{
public:
	// 智能指针特性一:RAII
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}

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

		delete _ptr;
	}

	// 智能指针特性二:像指针一样
	T& operator*()
	{
		return *_ptr;
	}

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

private:
	T* _ptr;
};
cpp 复制代码
Test.cpp
cpp 复制代码
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");

	return a / b;
}

void Func()
{
	SmartPtr<int> sp1(new int(1));
	SmartPtr<int> sp2(new int(0));

	*sp1 += 10;

	SmartPtr<pair<string, int>> sp3(new pair<string, int>);
	sp3->first = "apple";// 第一个箭头是operator->()函数中的箭头;第二个箭头:pair对象的指针
	sp3->second = 1;
	//sp3.operator->()->second = 1;

	cout << div() << endl;
}

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

	return 0;
}

总结一下智能指针的原理:

  1. RAII特性
  2. 重载operator*和opertaor->,具有像指针一样的行为。

3.3 std::auto_ptr

str::auto_ptr的文档

迭代器是访问修改;智能指针是释放资源

C++98版本的库中就提供了auto_ptr的智能指针。下面演示的auto_ptr的使用及问题。

auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份bit::auto_ptr来了解它的原理

cpp 复制代码
SmartPtr.h
cpp 复制代码
// auto_ptr的内部设计
namespace bit
{
	template<class T>
	class auto_ptr
	{
	public:
		// RAII
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}

		// ap2(ap1)
		auto_ptr(auto_ptr<T>& ap)
		{
			_ptr = ap._ptr;
			ap._ptr = nullptr;
		}

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

			delete _ptr;
		}

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

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

	private:
		T* _ptr;
	};
}
cpp 复制代码
Test.cpp
cpp 复制代码
#include"SmartPtr.h"

int main()
{
	bit::auto_ptr<int> sp1(new int(1));
	bit::auto_ptr<int> sp2(sp1);

	*sp2 += 10;

	// 悬空(sp1成空指针了)
	*sp1 += 10;

	return 0;
}

C++98 #include<memory> 智能指针的头文件
auto_ptr 管理权转移,被拷贝对象把资源管理权转移给拷贝对象,导致被拷贝对象悬空。注意拷贝过后不能访问被拷贝对象,否则就出现空指针了。很多公司禁止使用它,因为他很坑。

3.4 std::unique_ptr

C++11中开始提供更靠谱的unique_ptr

unique_ptr的文档

unique_ptr的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份UniquePtr来了解它的原理

C++11

  • unique_ptr 跟boost::scoped_ptr类似的,取名唯一指针
  • unique_ptr 禁止拷贝,简单粗暴,适用于不需要拷贝的场景。
cpp 复制代码
SmartPtr.h
cpp 复制代码
// 模拟实现unique_ptr
template<class T>
class unique_ptr
{
public:
	// RAII
	unique_ptr(T* ptr)
		:_ptr(ptr)
	{}

	// ap2(ap1)
	unique_ptr(const unique_ptr<T>& ap) = delete;// 禁止生成拷贝构造函数
	// 赋值是一个默认成员函数,不禁止生成,会默认生成,会发生浅拷贝,所以也要禁掉
	unique_ptr<T>& operator=(const unique_ptr<T>& ap) = delete;

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

		delete _ptr;
	}

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

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

private:
	T* _ptr;
};
cpp 复制代码
Test.cpp
cpp 复制代码
int main()
{
	bit::unique_ptr<int> sp1(new int(1));
	//bit::unique_ptr<int> sp2(sp1);

	bit::unique_ptr<int> sp2(new int(10));
	//sp1 = sp2;

	*sp2 += 10;

	*sp1 += 10;

	return 0;
}

3.5 std::shared_ptr

C++11中开始提供更靠谱的并且支持拷贝的shared_ptr

std::shared_ptr文档

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

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

先看一下错误的做法:

cpp 复制代码
shared_ptr的底层

需求:一个资源配置一份引用计数,而不是所有资源都用同一个引用计数
引用计数不是只有一份,是一个资源配置一份;现在又有一份资源,按理来说应该在配置一个引用计数,但是上面的不是,用的还是同一个引用计数;因为引用计数是静态的,静态的成员属于这个类,属于这个类的所有对象

再来看看正确的做法:

cpp 复制代码
shared_ptr的底层
cpp 复制代码
template<class T>
class shared_ptr
{
public:

	// RAII
	shared_ptr(T* ptr = nullptr)
		:_ptr(ptr)
		, _pcount(new int(1))
	{}

	// sp2(sp1)
	shared_ptr(const shared_ptr<T>& sp)
	{
		_ptr = sp._ptr;
		_pcount = sp._pcount;

		// 拷贝时++计数
		++(*_pcount);
	}

	// sp1 = sp4
	// sp4 = sp4;
	// sp1 = sp2;
	shared_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
		//if (this != &sp)
		if (_ptr != sp._ptr)
		{
			release();// 如果引用计数减到0了之后,就释放资源

			_ptr = sp._ptr;
			_pcount = sp._pcount;

			// 拷贝时++计数
			++(*_pcount);
		}

		return *this;
	}

	void release()
	{
		// 说明最后一个管理对象析构了,可以释放资源了
		if (--(*_pcount) == 0)
		{
			cout << "delete:" << _ptr << endl;
			delete _ptr;

			delete _pcount;
		}
	}

	~shared_ptr()
	{
		// 析构时,--计数,计数减到0,
		release();
	}

	int use_count()
	{
		return *_pcount;
	}

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

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

	T* get() const
	{
		return _ptr;
	}
private:
	T* _ptr;
	int* _pcount;// 一个指向计数的指针
};
cpp 复制代码
Test.cpp
cpp 复制代码
int main()
{
	bit::shared_ptr<int> sp1(new int(1));
	cout << sp1.use_count() << endl;// 引用计数

	bit::shared_ptr<int> sp2(sp1);
	cout << sp1.use_count() << endl;

	*sp2 += 10;
	*sp1 += 10;

	bit::shared_ptr<int> sp4(new int(5));

	bit::shared_ptr<int> sp3(sp2);
	cout << sp3.use_count() << endl;

    // 直接自己给自己赋值
	sp4 = sp4; 
 
    // 间接的自己给自己赋值
	// sp2和sp1虽然对象不同,但是都是管理的是同一份资源;
	// 同一份资源之间互相赋值,里面的过程就白做了,没有错误;
	// 可以判断资源所在的地址更好,不需要做多余动作了
	sp1 = sp2;

	sp1 = sp4;

	return 0;
}

3.6 std::shared_ptr的循环引用

cpp 复制代码
// shared_ptr的缺陷
struct ListNode
{
	int _val;
	//struct ListNode* _next;
	//struct ListNode* _prev;

	// shared_ptr<ListNode>类型创建的变量也要调用shared_ptr模板的构造函数
	bit::shared_ptr<ListNode> _next;
	bit::shared_ptr<ListNode> _prev;

	ListNode(int val = 0)
		:_val(val)
	{}

	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

int main()
{
	// 库里面的不允许隐式类型转换
	bit::shared_ptr<ListNode> n1(new ListNode(10));
	bit::shared_ptr<ListNode> n2(new ListNode(20));

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	//n1->_next = n2;
    // 库里面的这行代码编译不过去;因为右边是一个自定义类型shared_ptr<ListNode>,而左边是一个原生指针

	n1->_next = n2;
	n2->_prev = n1;

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	return 0;
}

_next是节点的成员,节点的空间什么时候释放,_next就什么时候析构

  1. n1和n2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动 delete。
  2. n1的_next指向n2,n2的_prev指向n1,引用计数变成2。
  3. n1和n2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上 一个节点。

循环引用:

  1. 左边的节点,右边的节点中_prev管着的,_prev析构,引用计数减到0,左边的节点就释放;
  2. 右边节点中_prev什么时候析构呢?右边的节点被delete时,_prev析构;
  3. 右边的节点什么时候delete呢?右边节点被左边节点的_next管着的,_next析构,右边的节点就释放了;
  4. 左边节点的_next什么时候析构呢?_next是左边节点的成员,左边节点delete释放时,_next就析构了;
  5. 左边节点什么时候释放呢?就又返回第一个点了,造成循环了。

3.7 采用std::weak_ptr来解决std::shared_ptr的循环引用问题

  • 弱指针weak_ptr,不增加引用计数
  • 不支持RAII,不参与资源管理
cpp 复制代码
SmartPtr.h
cpp 复制代码
template<class T>
class shared_ptr
{
public:

	// 如果function可以确定可调用对象的返回值和参数,便可对可调用对象进行封装
	// 无论是什么类型的删除器,删除器的返回值就是void,没有返回具体的类型
	// function<void(T*)> _del = [](T* ptr) {delete ptr; };
	//template<class D>
	//shared_ptr(T* ptr, D del)
	//	:_ptr(ptr)
	//	, _pcount(new int(1))
	//	, _del(del)
	//{}

	// RAII
	shared_ptr(T* ptr = nullptr)
		:_ptr(ptr)
		, _pcount(new int(1))
	{}

	// sp2(sp1)
	shared_ptr(const shared_ptr<T>& sp)
	{
		_ptr = sp._ptr;
		_pcount = sp._pcount;

		// 拷贝时++计数
		++(*_pcount);
	}

	// sp1 = sp4
	// sp4 = sp4;
	// sp1 = sp2;
	shared_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
		//if (this != &sp)
		if (_ptr != sp._ptr)
		{
			release();// 如果引用计数减到0了之后,就释放资源

			_ptr = sp._ptr;
			_pcount = sp._pcount;

			// 拷贝时++计数
			++(*_pcount);
		}

		return *this;
	}

	void release()
	{
		// 说明最后一个管理对象析构了,可以释放资源了
		if (--(*_pcount) == 0)
		{
			cout << "delete:" << _ptr << endl;
			delete _ptr;
			//_del(_ptr);
			// 包装器其实也是一个仿函数,包装器类型的对象可以用(),说明它重载了operator()函数,
			// 最终调用的是包装的仿函数的对象的operator()函数

			delete _pcount;
		}
	}

	~shared_ptr()
	{
		// 析构时,--计数,计数减到0,
		release();
	}

	int use_count()
	{
		return *_pcount;
	}

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

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

	T* get() const
	{
		return _ptr;
	}
private:
	T* _ptr;
	int* _pcount;// 一个指向计数的指针

	//function<void(T*)> _del = [](T* ptr) {delete ptr; };// 缺省值
	// 给一个默认的lambda,以免包装器内部为空
};

// 弱指针,不增加引用计数
// 不支持RAII,不参与资源管理
template<class T>
class weak_ptr  
{
public:
	// RAII
	weak_ptr()
		:_ptr(nullptr)
	{}

	weak_ptr(const shared_ptr<T>& sp)
	{
		_ptr = sp.get();// 只需要传对象的指针,不需要传引用计数,因为不析构,不参与资源的管理
	}

	weak_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
		// 在weak_ptr类中要访问shared_ptr类中的对象:友元函数、get()成员函数返回shared_ptr类对象的指针
		_ptr = sp.get();
		return *this;
	}

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

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

private:
	T* _ptr;
};
cpp 复制代码
Test.cpp
cpp 复制代码
struct ListNode
{
	int _val;

	bit::weak_ptr<ListNode> _next;
	bit::weak_ptr<ListNode> _prev;

	ListNode(int val = 0)
		:_val(val)
	{}

	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

int main()
{
	// 库里面的不允许隐式类型转换
	bit::shared_ptr<ListNode> n1(new ListNode(10));
	bit::shared_ptr<ListNode> n2(new ListNode(20));

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	//n1->_next = n2;
    // 库里面的这行代码编译不过去;因为右边是一个自定义类型shared_ptr<ListNode>,而左边是一个原生指针

	n1->_next = n2;
	n2->_prev = n1;

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	return 0;
}

其实weak_ptr也是有引用计数的,比如一个对象同时被weak_ptr和shared_ptr管理,假设shared_ptr的声明周期先到,那么这块空间应该被释放,那么当weak_ptr去访问时,就造成了野指针的问题,所以weak_ptr中也维护引用计数,只不过这个引用计数不参与计数,不参与这块空间的释放,而是用来观察该对象的引用计数到底是多少,shared_ptr声明周期先到,对象空间释放,但是引用计数先不释放,由weak_ptr来释放。

3.8 定制删除器

不管是我们自己实现的shared_ptr还是库中的shared_ptr,我们在析构的时候默认都是 delete _ptr,如果我们托管的类型是 new T[] ,或者 malloc出来的话,就导致类型不是匹配的,无法析构。

为此,shared_ptr提供了 定制删除器,我们可以在构造的时候作为参数传入。如果我们不传参,就默认使用delete。

给shared_ptr添加上定制删除器:

cpp 复制代码
SmartPtr.h
cpp 复制代码
template<class T>
class shared_ptr
{
public:

	// 如果function可以确定可调用对象的返回值和参数,便可对可调用对象进行封装
	// 无论是什么类型的删除器,删除器的返回值就是void,没有返回具体的类型
	// function<void(T*)> _del = [](T* ptr) {delete ptr; };
	template<class D>
	shared_ptr(T* ptr, D del)
		:_ptr(ptr)
		, _pcount(new int(1))
		, _del(del)
	{}

	// RAII
	shared_ptr(T* ptr = nullptr)
		:_ptr(ptr)
		, _pcount(new int(1))
	{}

	// sp2(sp1)
	shared_ptr(const shared_ptr<T>& sp)
	{
		_ptr = sp._ptr;
		_pcount = sp._pcount;

		// 拷贝时++计数
		++(*_pcount);
	}

	// sp1 = sp4
	// sp4 = sp4;
	// sp1 = sp2;
	shared_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
		//if (this != &sp)
		if (_ptr != sp._ptr)
		{
			release();// 如果引用计数减到0了之后,就释放资源

			_ptr = sp._ptr;
			_pcount = sp._pcount;

			// 拷贝时++计数
			++(*_pcount);
		}

		return *this;
	}

	void release()
	{
		// 说明最后一个管理对象析构了,可以释放资源了
		if (--(*_pcount) == 0)
		{
			cout << "delete:" << _ptr << endl;
			delete _ptr;
			//_del(_ptr);
			// 包装器其实也是一个仿函数,包装器类型的对象可以用(),说明它重载了operator()函数,
			// 最终调用的是包装的仿函数的对象的operator()函数

			delete _pcount;
		}
	}

	~shared_ptr()
	{
		// 析构时,--计数,计数减到0,
		release();
	}

	int use_count()
	{
		return *_pcount;
	}

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

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

	T* get() const
	{
		return _ptr;
	}
private:
	T* _ptr;
	int* _pcount;// 一个指向计数的指针

	function<void(T*)> _del = [](T* ptr) {delete ptr; };// 缺省值
	// 给一个默认的lambda,以免包装器内部为空
};

// 弱指针,不增加引用计数
// 不支持RAII,不参与资源管理
template<class T>
class weak_ptr  
{
public:
	// RAII
	weak_ptr()
		:_ptr(nullptr)
	{}

	weak_ptr(const shared_ptr<T>& sp)
	{
		_ptr = sp.get();// 只需要传对象的指针,不需要传引用计数,因为不析构,不参与资源的管理
	}

	weak_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
		// 在weak_ptr类中要访问shared_ptr类中的对象:友元函数、get()成员函数返回shared_ptr类对象的指针
		_ptr = sp.get();
		return *this;
	}

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

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

private:
	T* _ptr;
};
cpp 复制代码
Test.cpp
cpp 复制代码
struct ListNode
{
	int _val;

	bit::weak_ptr<ListNode> _next;
	bit::weak_ptr<ListNode> _prev;

	ListNode(int val = 0)
		:_val(val)
	{}

	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

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

// 定制删除器,就是可调用对象(可以是函数指针、仿函数、lambda)
int main()
{
	std::shared_ptr<ListNode> p1(new ListNode(10));// 管理单个对象没有问题
	//std::shared_ptr<ListNode[]> p2(new ListNode[10]);// 库里面是可以管理多个对象的

	std::shared_ptr<ListNode> p2(new ListNode[10], DeleteArray<ListNode>());// 释放时用仿函数DeleteArray的对象去释放
 
    // 也可以使用lambda的方式
	std::shared_ptr<FILE> p3(fopen("Test.cpp", "r"), [](FILE* ptr) {fclose(ptr); });

	return 0;
}
cpp 复制代码
int main()
{
	bit::shared_ptr<ListNode> p1(new ListNode(10));// 没有给删除器
    // 这里因为没有给删除器,因此走的是没有给删除器的构造函数,
	// 那么包装器调用的就是没有可调用对象的默认构造函数,那么释放的时候会出问题,所以给包装器一个缺省值
 
	// 仿函数对象传到shared_ptr的构造函数之后,function对它进行包装
	bit::shared_ptr<ListNode> p2(new ListNode[2], DeleteArray<ListNode>());
	bit::shared_ptr<FILE> p3(fopen("Test.cpp", "r"), [](FILE* ptr) {fclose(ptr); });

	return 0;
}

四、C++11和boost中智能指针的关系

  1. C++ 98 中产生了第一个智能指针auto_ptr.
  2. C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
  3. C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
  4. C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost 的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。
cpp 复制代码
int main()
{
	char* p1 = new char[1024 * 1024 * 1024];
	// 本意是想打印p1的指针,但是char*会认为你想打印字符串,
	// 所以会解引用打印字符串,遇到\0截至
	cout << p1 << endl;
	cout << (void*)p1 << endl;// 凡是打印char*类型的指针,都要强转成void*的类型

	return 0;
}

总结

好了,本篇博客到这里就结束了,如果有更好的观点,请及时留言,我会认真观看并学习。

不积硅步,无以至千里;不积小流,无以成江海。

相关推荐
小字节,大梦想37 分钟前
【C++】二叉搜索树
数据结构·c++
吾名招财38 分钟前
yolov5-7.0模型DNN加载函数及参数详解(重要)
c++·人工智能·yolo·dnn
XKSYA(小巢校长)1 小时前
NatGo我的世界联机篇
开发语言·php
Cons.W1 小时前
Codeforces Round 975 (Div. 1) C. Tree Pruning
c语言·开发语言·剪枝
我是哈哈hh1 小时前
专题十_穷举vs暴搜vs深搜vs回溯vs剪枝_二叉树的深度优先搜索_算法专题详细总结
服务器·数据结构·c++·算法·机器学习·深度优先·剪枝
憧憬成为原神糕手1 小时前
c++_ 多态
开发语言·c++
VBA63371 小时前
VBA信息获取与处理第三个专题第三节:工作薄在空闲后自动关闭
开发语言
郭二哈1 小时前
C++——模板进阶、继承
java·服务器·c++
挥剑决浮云 -1 小时前
Linux 之 安装软件、GCC编译器、Linux 操作系统基础
linux·服务器·c语言·c++·经验分享·笔记
丶Darling.2 小时前
LeetCode Hot100 | Day1 | 二叉树:二叉树的直径
数据结构·c++·学习·算法·leetcode·二叉树