【C/C++】智能指针

2023-10-26


在这里插入图片描述

1.智能指针的原理

1.1RAII

​ RAII叫做资源获取即初始化,是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。 通常是将资源封装到一个类中,将资源管理的责任交给一个对象。

  • 不需要显式地释放资源。
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效
1.2实现一个自己的智能指针
template<class T>
class smartprt
{
public:
	smartprt(T* ptr)
		:_ptr(ptr)
	{}
    smartptr(smartptr<T>&ptr)
		:_ptr(ptr)
	{}
	~smartprt()
	{
		cout << "delete:" << ptr << endl;
		delete _ptr; //释放内存
		_ptr = nullptr;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	T* get()
	{
		return _ptr;
	}
private:
	T* _ptr;
};

测试

struct Date
{
	int _year;
	int _month;
	int _day;
};
int main()
{
	smartptr<int>ptr1(new int);
	*ptr1 = 10;
	cout << *ptr1 << endl;
	smartptr<Date>date(new Date);
    /*
    	这一步编译器进行了优化,原调用应该是
    	date.operator->()->_year;  编译器为了可读性
    	省略了operator->()的调用
    */
	date->_year = 2022;
	date->_month = 9;
	date->_day = 22;
	cout << "year:" << date->_year << endl;
	cout << "_month:" << date->_month << endl;
	cout << "_day:" << date->_day << endl;
	return 0;
}

在这里插入图片描述

1.2.1拷贝出现的二次析构问题

​ 上面smartptr类,会出现二次析构问题。(比如我们拷贝一个指针,那么指针指向的内存可能被释放多次)

void func()
{
	smartptr<Date>date1(new Date);
	date1->_year = 2022;
	date1->_month = 9;
	date1->_day = 22;
	smartptr<Date>date3 = date1;
}
int main()
{
	func();
	return 0;
}

在这里插入图片描述

2.标准库中的智能指针

2.1std::auto_ptr

​ C++98版本的库中就提供了auto_ptr的智能指针。其核心思想是将内存的管理权移交。【结果是无论如何拷贝,都只有一个指针可以对内存进行管理】。

下面实现一个简化版本的auto_ptr

template<class T>
class myauto_ptr
{
	typedef myauto_ptr<T> self;
public:
	//构造函数
	myauto_ptr(T*ptr):_ptr(ptr)
	{}
	~myauto_ptr()
	{
		if (_ptr)
		{
			cout << "delete" << _ptr << endl;
			delete _ptr;
			_ptr = nullptr;
		}
	}
	//拷贝构造,移交管理权
	myauto_ptr(myauto_ptr<T>&au)
		:_ptr(au._ptr)
	{
		au._ptr = nullptr;
	}
    //赋值运算符重载
    myauto_ptr<T>& operator=(myauto_ptr<T>&aut)
	{
		_ptr = aut._ptr;
		aut._ptr = nullptr;
		return *this;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	T* get()
	{
		return _ptr;
	}
private:
	T* _ptr;
};

测试

void fun2()
{
	myauto_ptr<Date>date1(new Date);
	date1->_year = 2022;
	date1->_month = 9;
	date1->_day = 22;
	myauto_ptr<Date>date2(date1);
	cout << "date2->_year" << date2->_year << endl;
	cout << "date2->month" << date2->_month << endl;
	cout << "date2->day" << date2->_day << endl;
	cout << "date1->_year" << date1->_year << endl;
	cout << "date1->month" << date1->_month << endl;
	cout << "date1->day" << date1->_day << endl;
}
int main()
{
	fun2();
	return 0;
}

在这里插入图片描述

表明只有一个指针具有管理和访问内存的权限

2.2std::unique_ptr

unique_ptr的实现原理:简单粗暴的防拷贝。

template<class T>
class myunique_ptr
{
public:
	//构造函数
	myunique_ptr(T* ptr) :_ptr(ptr)
	{}
	~myunique_ptr()
	{
		if (_ptr)
		{
			cout << "delete" << _ptr << endl;
			delete _ptr;
			_ptr = nullptr;
		}
	}
	//防止拷贝
    //方法一:使用关键字delete
	//方法二:自己在类中声明并实现什么都不干的拷贝构造和赋值运算符重载
	myunique_ptr(const myunique_ptr<T>& q) = delete;
	myunique_ptr<T>& operator=(const myunique<T>& sp) = delete;
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	T* get()
	{
		return _ptr;
	}
private:
	T* _ptr;
};
2.3std::shared_ptr

C++中第一个好用的智能指针。

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

  • shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
  • 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减1
  • 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
  • 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了
template<class t>
class myshared_ptr
{
public:
	void release()
	{
		if (--(*_count) == 0 && _ptr)
		{
			cout << "delete" << _ptr << endl;
			delete _ptr;
			_ptr = nullptr;
			delete _count;
			_count = nullptr;
		}
	}
	myshared_ptr(t* ptr)
		:_ptr(ptr)
		,_count(new int(1))
	{}
	~myshared_ptr()
	{
		release();
	}
	t& operator*()
	{
		return *_ptr;
	}
	t* operator->()
	{
		return _ptr;
	}
	t* get()
	{
		return _ptr;
	}
	myshared_ptr(const myshared_ptr<t>& sp)
		:_ptr(sp._ptr)
		, _count(sp._count)
	{
		(*_count)++;
	}
	//赋值运算符重载
	myshared_ptr<t>& operator=(const myshared_ptr<t>& sp)
	{
		//if (this != &sp)
		if (_ptr != sp._ptr)
		{
			release();
			_ptr=sp._ptr;
			_count =sp._count;
			++(*_count);
		}
		return *this;
	}
private:
	t* _ptr;
	int* _count;
};

测试

void fun3()
{
	myshared_ptr<Date>date1(new Date);
	date1->_year = 2022;
	date1->_month = 9;
	date1->_day = 22;
	myshared_ptr<Date>date2(date1);
	myshared_ptr<Date>date3 = date1;
	cout << "date2->_year" << date2->_year << endl;
	cout << "date2->month" << date2->_month << endl;
	cout << "date2->day" << date2->_day << endl;
	cout << "date1->_year" << date1->_year << endl;
	cout << "date1->month" << date1->_month << endl;
	cout << "date1->day" << date1->_day << endl;
}
int main()
{
	fun3();
	return 0;
}

在这里插入图片描述

2.4shared_ptr的线程安全问题
  1. 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或–,这个操作不是原子的,引用计数原来是1,++了两次,可能还是
  2. 这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、–是需要加锁的,也就是说引用计数的操作是线程安全的。
  3. 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。

解决方法:对_count的操作都加锁

template<class t>
class myshared_ptr
{
public:
	void release()
	{
        _pmtx->lock();
		if (--(*_count) == 0 && _ptr)
		{
			cout << "delete" << _ptr << endl;
			delete _ptr;
			_ptr = nullptr;
			delete _count;
			_count = nullptr;
		}
        _pmtx->unlock();
	}
	myshared_ptr(t* ptr)
		:_ptr(ptr)
		,_count(new int(1))
         , _pmtx(new mutex)   
	{}
	~myshared_ptr()
	{
		release();
	}
	t& operator*()
	{
		return *_ptr;
	}
	t* operator->()
	{
		return _ptr;
	}
	t* get()
	{
		return _ptr;
	}
	myshared_ptr(const myshared_ptr<t>& sp)
		:_ptr(sp._ptr)
		, _count(sp._count)
         , _pmtx(sp._pmtx)
	{
         _pmtx->lock();
		(*_count)++;
        _pmtx->unlock();
	}
	//赋值运算符重载
	myshared_ptr<t>& operator=(const myshared_ptr<t>& sp)
	{
		//if (this != &sp)
		if (_ptr != sp._ptr)
		{
			release();
			_ptr=sp._ptr;
			_count =sp._count;
            _pmtx->lock();
			++(*_count);
            _pmtx->unlock();
		}
		return *this;
	}
private:
	t* _ptr;
	int* _count;
    mutex* _pmtx;
};
2.5循环引用和weak_ptr
2.5.1循环引用

shared_ptr虽然功能交其他的智能指针功能更加强大,但是同样有其问题,其中一个就是循环引用的问题。如:

struct ListNode
{
	std::shared_ptr<ListNode> prev = nullptr;
	std::shared_ptr<ListNode> next = nullptr;
	int _val = 0;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};
int main()
{
	std::shared_ptr<ListNode>ptr1(new ListNode);
	std::shared_ptr<ListNode>ptr2(new ListNode);
	ptr1->next = ptr2;
	ptr2->prev = ptr1;
	return 0;
}

按照预期,ptr1和ptr2析构时会分别释放对应的ListNode结构,但结果是并没有释放内存

在这里插入图片描述

shared_ptr的底层是实现的引用计数,上面的代码逻辑为:

在这里插入图片描述

当main函数结束,ptr1和ptr2发生析构时:

在这里插入图片描述

2.5.2weak_ptr

为了解决shared_ptr出现的循环引用问题,C++11引入了weak_ptr。

weak_ptr可以访问对应的内存,但是不参与资源的管理。

struct ListNode
{
	std::weak_ptr<ListNode> prev ;
	std::weak_ptr<ListNode> next ;
	int _val = 0;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};
int main()
{
	std::shared_ptr<ListNode>ptr1(new ListNode);
	std::shared_ptr<ListNode>ptr2(new ListNode);
	ptr1->next = ptr2;
	ptr2->prev = ptr1;
	return 0;
}

在这里插入图片描述

2.5.3weak_ptr模拟实现

weak_ptr的底层实现很简单,可以访问对应的内存,但是不参与资源的管理。

template <class T>
class myweak_ptr
{
    myweak_ptr()
        :_ptr(nullptr)
     {}
   	myweak_ptr(const shared_ptr<T>& sp)
    	:_ptr(sp.get())
    {}
    myweak_ptr<T>& operator(const shared_ptr<T>&sp)
    {
        if(_ptr!=sp.get())
        {
            _ptr=sp.get();
        }
        return *this;
    }
private:
    T* _ptr;
}

3.定制删除器

智能指针默认释放资源的方式是delete。

由于无法删除数组类型的指针或者FILE*的类型,所以出现了定制删除器。

struct  Date
{
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};
int main()
{
	std::unique_ptr<Date>date1(new Date);
	std::unique_ptr<Date>date2(new Date[10]);
	std::unique_ptr<Date>date3((Date*)malloc(sizeof(Date) * 5));
	//std::unique_ptr<FILE>ptr1((FILE*)fopen("test.cpp", "r"));
	return 0;
}

在这里插入图片描述

发生了内存泄漏

定制删除器的底层就是一个仿函数

struct  Date
{
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

template <class T>
struct  del
{
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};
template <class T>
struct  freed
{
	void operator()(T* ptr)
	{
		free(ptr);
	}
};
int main()
{
	std::unique_ptr<Date>date1(new Date);
	std::unique_ptr<Date,del<Date>>date2(new Date[10]);
	std::unique_ptr<Date,freed<Date>>date3((Date*)malloc(sizeof(Date) * 10));
	//std::unique_ptr<FILE>ptr1((FILE*)fopen("test.cpp", "r"));
	return 0;
}

在这里插入图片描述

delete的底层是先调用析构函数,在调用operator delete。而operator delete又封装了free()

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【C/C++】智能指针 的相关文章

  • 从文本框值插入数字(十进制)数据

    我对以下问题感到困惑 我有一个 C Windows 窗体 应用程序 它连接到 SQL Server DB 并且在 INSERT SELECT UPDATE 方面没有任何问题 直到我开始处理数字数据 这个应用程序的目的是管理员工 他们的合同
  • 在 C# 中将字符串值转换为运算符

    我正在尝试找出一种动态构建条件的方法 到目前为止 这是我的代码 var greaterThan gt var a 1 var b 2 if a Convert ToOperator greaterThan b 我确实读过这篇文章 但不知道如
  • displayformatattribute 自定义字符串格式

    我希望能够将视图模型中的属性标记为在 MVC 视图中显示时使用自定义格式进行格式化 我假设我需要自己的显示格式属性并设置显示格式 但我不确定如何设置此显示格式或在哪里设置 如果我想将数字显示为货币 很简单 只需在属性的构造函数中将 Data
  • 使用字典作为数据源绑定组合框

    我正在使用 NET 2 0 并且尝试将组合框的数据源绑定到已排序的字典 所以我收到的错误是 在数据源上找不到 DataMember 属性 Key SortedDictionary
  • 使用curl解压gzip数据

    I added curl easy setopt client CURLOPT ENCODING gzip 到我的代码 我预计curl 会导致服务器发送压缩数据并解压缩它 实际上我在 HTTP 标头中看到数据被压缩 变化 Accept En
  • 确保始终捕获异常

    C 中的异常不需要被调用函数捕获 没有编译时错误 因此 是否使用 try catch 来捕获它们取决于开发人员的判断 与 Java 不同 有没有一种方法可以确保调用函数始终使用 try catch 捕获抛出的异常 No See 务实地看待异
  • 错误“标记不是预处理器子表达式中的有效二元运算符”

    如果我构建并运行一个项目 基本上是由 Mac OS 10 6 上的 Qt 框架生成的存根 我会得到以下错误输出 Users home Qt5 0 1 5 0 1 clang 64 include QtCore qisenum h 53 Er
  • 从 SQLDataReader 读取结果时出现无效转换异常

    我的存储过程 UserName nvarchar 64 AS BEGIN SELECT MPU UserName SUM TS Monday as Monday TS Monday contains float value FROM dbo
  • 基类和派生类中的数据成员相同

    我是 C 编程新手 我正在阅读继承概念 我对继承概念有疑问 如果基类和派生类具有相同的数据成员 会发生什么 另外请仔细阅读我的代码 如下所示 include stdafx h include
  • 找出数组中重复的元素

    有一个大小为 n 的数组 数组中包含的元素在 1 到 n 1 之间 每个元素出现一次 只有一个元素出现多次 我们需要找到这个元素 尽管这是一个非常常见的常见问题解答 但我仍然没有找到正确的答案 大多数建议是我应该将数组中的所有元素相加 然后
  • Windows 操作系统中无法访问的 IP 套接字关闭时间

    这些代码通过用户数据报协议提供发送数据 下面有两个代码 当我使用第一个代码来处理无法访问的 IP 地址时 我得到了三秒的延迟 请查看新结果标题 只需打开新的 C 控制台应用程序并将这些代码粘贴到其中 第一个代码 using System u
  • 使用 libsoup 进行 HTTP POST

    我想使用 libsoup 执行一个简单的 POST 请求 我想要发送数据的网站的 api 只需要一个名为 内容 的字段 使用curl我这样做 curl si F content mycontent http mywebsite org ap
  • 将 LINQ 序列中的项目发送到返回 void 的方法

    通常 当我处理 LINQ 序列时 我希望将每个项目发送到返回 void 的方法 从而避免 foreach 循环 但是 我还没有找到一种优雅的方法来做到这一点 今天 我写了如下代码 private StreamWriter sw privat
  • 实体框架的审计跟踪

    我在每个表中都有审计跟踪字段 InsertedBy InsertedDate UpdatedBy 和 UpdatedDate 我构建解决方案以通过覆盖 savechange 来减少冗余 public override int SaveCha
  • 如何使用 Azure Active Directory Graph API 获取属于 AppRole 的所有用户

    我一生都无法弄清楚如何查询 Azure Active Directory 的图形 API 来获取属于特定 AppRole 的所有用户 首先我尝试了类似的东西 client Users Where u gt u AppRoleAssignme
  • pictureBox 图片处理异常

    我最近想尝试一下锻造网 http www aforgenet com framework 因为我发现它非常简单 所以我决定使用 Video FFMPEG 命名空间进行一些简单的视频播放 这样我就可以将每个帧直接放在 pictureBox 上
  • int q = {1,2};特殊的初始化列表

    我遇到了下面的初始化 可以看出VS2012 显示一个错误 抱怨初始化程序太多 在海湾合作委员会看来 返回第一个元素作为值 为什么 GCC 支持这种特殊的初始化 include
  • 无法从 cin.get() 获取 char

    我正在做一些关于 C 的初学者练习 这让我很困惑 我可以输入数字 但之后无法选择输入字符 并且会跳到最后一行 我知道我可以使用 cin gt gt 符号 但我想知道为什么这不起作用 include
  • 返回常量引用和右值引用之间的区别

    如果我没记错的话 我认为 const 引用和右值引用都可以绑定到右值 返回前者的函数和返回后者的函数之间有什么实际区别吗 编辑 我无法修改前者 但为什么我会对修改右值感兴趣 是否有意义 A const左值引用可以绑定到任何东西 右值引用只能
  • C 语言中这个奇怪的函数指针声明是什么意思? [复制]

    这个问题在这里已经有答案了 谁能解释一下什么int foo int int 在这呢 int fooptr int int foo int int Can t understand what this does int main fooptr

随机推荐

  • Flex应用程序启动详解

    编写一个简单的Flex应用程序并不复杂 就算你从来没接触过Flex程序设计 照着帮助的实例步骤 不需花多长时间也能做出一个漂亮简捷的小程序出来 不过 随着对Flex程序编写的深入 会越来越觉得 其实要编写一个好的Flex应用程序并不简单 涉
  • uniapp切片-可视化设计工具(一套代码编译到7个平台iOS、Android、H5、小程序)

    uni app 是一个使用 Vue js 开发跨平台应用的前端框架 开发者编写一套代码 可编译到iOS Android H5 小程序等多个平台 一套代码编到7个平台 难以置信吗 依次扫描7个二维码 亲自体验最全面的跨平台效果 uni app
  • C++之sort()函数详解,刷题必备~

    顾名思义 sort就是用来排序的函数 它根据具体情形使用不同的排序方法 效率较高 一般来说 不推荐使用C语言中的qsort函数 原因是qsort用起来比较烦琐 涉及很多指针的操作 而且sort在实现中规避了经典快速排序中可能出现的会导致实际
  • C# 中的sealed修饰符学习

    转载原地址 http developer 51cto com art 200908 147327 htm C 语言还是比较常见的东西 这里我们主要介绍C sealed修饰符 包括介绍两个修饰符在含义上互相排斥用于方法和属性等方面 C sea
  • python爬虫网络出错怎么办_Python爬虫异常处理

    100 继续 客户端应当继续发送请求 客户端应当继续发送请求的剩余部分 或者如果请求已经完成 忽略这个响应 101 转换协议 在发送完这个响应最后的空行后 服务器将会切换到在Upgrade 消息头中定义的那些协议 只有在切换新的协议更有好处
  • linux上redis常用命令以及遇到的问题

    1 在linux上解压缩后使用make命令进行编译的时候 错误类型 zmalloc h 50 31 致命错误 jemalloc jemalloc h 没有那个文件或目录 原因是因为编译的时候Linux默认内存分配器是jemalloc 而Re
  • 添加商品到购物车 Vuex

    商品详情 购物车页面 code
  • OFDM插入导频过程详解

    ofdm符号的长度 有效数据 cp的长度 cp就是将有效数据的后半部分1 4截取并添加到有效数据的开始部分 比如一个ofdm符号的长度为4us 那么有效数据的长度为3 2us cp的长度为0 8us 子载波的间隔 1 有效数据的长度 就是有
  • Unity使用c#开发遇上的问题(十三)(unity平台下使用 Vuforia 以及 ARFoundiation 的总结,根据个人观点)

    文章目录 前言 一 Vuforia的使用感觉 二 ARfoundiation的使用感觉 总结 前言 有一段时间没有更新系列的内容 上次更新完又重新思考了一下以后进行的方向 这里就目前接触的Vuforia 和 unity 自带的AR 之前叫A
  • 自动生成根据mysql表创建hive表脚本

    bin bash source etc profile 该脚本为手动传参根据MySQL表信息创建hive表 输入参数判断逻辑 必须数据两个参数 一个是MySQL库名 第二个是表名 if eq 2 then db name 1 mysql 库
  • 浅析java垃圾回收机制

    一 什么是垃圾回收 1 垃圾回收 顾名思义 便是将已经分配出去的 但却不再使用的内存回收回来 以便能够再次分配 在 Java 虚拟机的语境下 垃圾指的是死亡的对象所占据的堆空间 垃圾回收只会负责释放那些对象占有的内存 此时对象也就被销毁 2
  • 0长度数组的使用,重点掌握的知识

    0长度的数组在ISO C和C 的规格说明书中是不允许的 但是GCC的C99支持的这种用法 GCC对0长度数组的文档参考 Arrays of Length Zero 如下代码片段 哪个更简洁更灵活 看一眼就知道了 include
  • 用vscode开发autojs,输出窗口不显示任何输出结果

    我的情况是 我vscode开发autojs 程序 之前在一切正常的情况下 输出窗口可以正常显示程序运行结果 右侧红圈里可以选择我连接的手机型号 如下图 但是现在出现问题 就是输出窗口不显示任何结果 在右侧的选项卡里也找不到我的手机型号 之前
  • 2021年全球与中国龙胆苦苷行业市场规模及发展前景分析

    2021年全球与中国龙胆苦苷行业市场规模及发展前景分析 本报告研究全球与中国市场龙胆苦苷的发展现状及未来发展趋势 分别从生产和消费的角度分析龙胆苦苷的主要生产地区 主要消费地区以及主要的生产商 重点分析全球与中国市场的主要厂商产品特点 产品
  • (R,线性回归)R语言里的模型诊断图(Residuals vs Fitted,Normal QQ , Scale-Location ,Residuals Leverage)

    线性回归 是概率统计学里最重要的统计方法 也是机器学习中一类非常重要的算法 线性模型简单理解非常容易 但是内涵是非常深奥的 尤其是线性回归模型中的Diagnostics plot的阅读与理解一直被认为是线性回归中的一个难点 在任何线性模型中
  • 获取微信公众号地址的图片不能正常显示的问题

    获取微信公众号地址的图片不能正常显示的问题 目前已经获取微信公众号发布的图片 但不能正常显示 提示 此图片来自微信公众平台 未经允许不得引用 看了一下他的地址是这样的 https mmbiz qpic cn mmbiz jpg ic70qV
  • Codeforces Round #291 (Div. 2)

    题目链接contest 514 A Chewba ca and Number 不允许有前导零 所以如果第一位是9的话 需要特别考虑 一开始理解错了题意 又WA了呜呜呜 include
  • 弱密码测试工具blaster使用演示

    声明 出品 安全客 以下内容 来自安全客作者原创 由于传播 利用此文所提供的信息而造成的任何直接或间接的后果和损失 均由使用者本人负责 长白山攻防实验室以及文章作者不承担任何责任 关于blaster blaster是一款强大的弱密码隐患检测
  • RabbitMQ-Java 简单使用

    RabbitMQ Java 入门案例 参考非常详细的博主教程 https www cnblogs com dtdx p 14362760 html SpringBoot Java 版教程 https blog csdn net lgl782
  • 【C/C++】智能指针

    文章目录 1 智能指针的原理 1 1RAII 1 2实现一个自己的智能指针 1 2 1拷贝出现的二次析构问题 2 标准库中的智能指针 2 1std auto ptr 2 2std unique ptr 2 3std shared ptr 2