「前言」文章内容大致是智能指针。

一、 为何需要智能指针

1.1 内存泄漏问题

关于内存泄漏的问题,例如下面的代码:

#include <iostream>
using namespace std;

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	int* p1 = new int;
    int* p2 = new int;
	cout << div() << endl;
	delete p1;
    delete p2;
}
int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

执行上述代码时,如果输入的除数为 0,那么 div 函数中就会抛出异常,这时程序的执行流会直接跳转到主函数中的 catch 块中执行,最终导致 Func 函数中申请的内存资源没有得到释放。

对于这种情况,我们可以在 Func 函数中先对 div 函数中抛出的异常进行捕获,捕获后先将之前申请的内存资源释放,然后再将异常重新抛出,代码如下:

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	int* p1 = new int;
    int* p2 = new int;
	try
	{
		cout << div() << endl;
	}
	catch (...)
	{
		delete p1;
        delete p2;
		throw;
	}
	delete p1;
    delete p2;
}
int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

new 也会抛异常的,如果 p1 这里 new 抛异常,这时程序的执行流会直接跳转到主函数中的 catch 块中执行,这里没有问题。假设 p1 没有问题,那如果 p2 的 new 也抛异常呢?需要再嵌套一个 try、catch语句??

void Func()
{
	int* p1 = new int;
	int* p2 = nullptr;
	try
	{
		int* p2 = new int;//p2的new可能会抛异常
		try
		{
			cout << div() << endl;
		}
		catch (...)
		{
			delete p1;
			delete p2;
			throw;
		}
	}
	catch (...)
	{
		delete p1;
		throw;
	}
	delete p1;
	delete p2;
}

这样的代码显得很 low,疯狂嵌套 try、catch 语句,所以为了解决这个问题就出现的智能指针。

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

  • 堆内存泄漏(Heap leak):堆内存指的是程序执行中依据须要分配通过 malloc/calloc/realloc/new 等从堆中分配的一块内存,用完后必须通过调用相应的 free 或者 delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
  • 系统资源泄漏:指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定

内存泄漏非常常见,解决方案分为两种:

  1. 事前预防型。如智能指针等。
  2. 事后查错型。如泄漏检测工具。

1.2 使用智能指针解决

什么是智能指针??

  • 把申请到的内存空间交给了一个 SmartPtr 进行管理。
  • 在构造 SmartPtr 对象时,把需要被管理的内存空间传入 SmartPtr 的对象中。
  • 在 SmartPtr 对象消亡时,SmartPtr 的析构函数中会自动将管理的内存空间进行释放。
  • 如果出现抛异常的情况,申请的空间会随着 SmartPtr 对象的生命周期而释放,内存泄漏的问题可以得到很好的解决。
  • 为了让 SmartPtr 对象能够像原生指针一样使用,还需要对 *和 ->运算符进行重载。

例如:

//智能指针
template<class T>
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<int> sp1(new int);
	SmartPtr<int> sp2(new int);
	cout << div() << endl;
}

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

上面的代码中将申请到的内存空间交给了一个 SmartPtr 的对象 sp1、sp2 进行管理,这样一来,无论程序是正常执行完毕返回了,还是因为某些原因中途返回了,或是因为抛异常返回了,只要 SmartPtr 对象的生命周期结束就会调用其对应的析构函数,进而完成内存资源的释放。

1.3 智能指针的原理

(1)RAII

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

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

RAII 其核心思想是:资源的获取(Acquisition)与对象的初始化绑定在一起,资源的释放(Release)与对象的销毁绑定在一起。通过这种方式,可以确保资源的正确管理,避免资源泄露,同时使代码更加简洁和易于维护。

这种做法有两大好处:

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

比如:

// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
	}
private:
	T* _ptr;
};

(2)像指针行为

上述(1)的 SmartPtr 还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过 -> 去访问所指空间中的内容,因此:智能指针中还得需要将 * 和 -> 重载一下,才可让其像指针一样去使用。

template<class T>
class SmartPtr 
{
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
	}
	T& operator*() { return *_ptr; }
	T* operator->() { return _ptr; }
private:
	T* _ptr;
};

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

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

但是这样的智能指针还不够完善,会存在智能指针对象拷贝的问题,所以C++出现了不同版本的智能指针。

决智能指针对象的拷贝问题:比如上面实现的智能指针 SmartPtr类,如果用一个 SmartPtr 对象来拷贝构造另一个 SmartPtr 对象,或是将一个 SmartPtr 对象赋值给另一个SmartPtr对象,都会导致程序崩溃。

int main()
{
	SmartPtr<int> sp1(new int);
	SmartPtr<int> sp2(sp1); //拷贝构造

	SmartPtr<int> sp3(new int);
	SmartPtr<int> sp4(new int);
	sp3 = sp4; //拷贝赋值
	
	return 0;
}

原因:

  • 编译器默认生成的拷贝构造函数对内置类型完成值拷贝(浅拷贝),因此用 sp1 拷贝构造 sp2 后,相当于这 sp1 和 sp2 管理了同一块内存空间,当 sp1 和 sp2 析构时就会导致这块空间被释放两次。
  • 编译器默认生成的拷贝赋值函数对内置类型也是完成值拷贝(浅拷贝),因此将 sp4 赋值给 sp3 后,相当于 sp3 和 sp4 管理的都是原来 sp3 管理的空间,当 sp3 和 sp4 析构时就会导致这块空间被释放两次,并且还会导致 sp4 原来管理的空间没有得到释放。

二、C++的智能指针

2.1 std::auto_ptr

C++98版本的库中就提供了 auto_ptr 的智能指针,文档介绍:std::auto_ptr

// 头文件:
#include <memory> 

auto_ptr 通过管理权转移的方式解决智能指针的拷贝问题,保证一个资源在任何时刻都只有一个对象在对其进行管理,这时同一个资源就不会被多次释放了。

测试代码如下:

int main()
{
	std::auto_ptr<int> ap1(new int(1));
	std::auto_ptr<int> ap2(ap1);
	*ap2 = 10;

	std::auto_ptr<int> ap3(new int(1));
	std::auto_ptr<int> ap4(new int(2));
	ap3 = ap4;

	return 0;
}

进行调试查看:

对一个对象的管理权转移后也就意味着,该对象不能再用对原来管理的资源进行访问了,会造成对象悬空,比如上面的 sp1、sp2,继续使用这两个对象程序就会直接崩溃,因此使用 auto_ptr 之前必须先了解它的机制,否则程序很容易出问题。

auto_ptr 是一个失败设计,很多公司也都明确规定了禁止使用 auto_ptr,在 C++11 已经弃用。

auto_ptr 的简单模拟实现

namespace fy
{
	template<class T>
	class auto_ptr
	{
	public:
		// RAII
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}
		auto_ptr(auto_ptr<T>& sp)
			:_ptr(sp._ptr)
		{
			sp._ptr = nullptr;// 管理权转移
		}
		auto_ptr<T>& operator=(auto_ptr<T>& ap)
		{
			if (this != &ap)// 检测是否为自己给自己赋值
			{
				// 释放当前对象中资源
				if (_ptr)
					delete _ptr;
				// 转移ap中资源到当前对象中
				_ptr = ap._ptr;
				ap._ptr = NULL;
			}
			return *this;
		}
		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}
		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};
}

2.2 std::unique_ptr

unique_ptr是C++11中引入的智能指针,unique_ptr通过防拷贝的方式解决智能指针的拷贝问题,也就是简单粗暴的防止对智能指针对象进行拷贝,这样也能保证资源不会被多次释放。

unique_ptr 是一种独占式智能指针,它拥有对对象的唯一所有权,不能被多个 unique_ptr 对象共享。当 unique_ptr 对象被销毁时,它所管理的对象也会被自动销毁。

// 头文件:
#include <memory> 

文档介绍:unique_ptr

 测试代码

int main()
{
	std::unique_ptr<int> up1(new int(1));
	std::unique_ptr<int> up2(up1); //error,不允许拷贝
	return 0;
}

编译报错

unique_ptr 简单模拟实现 

namespace fy
{
	template<class T>
	class unique_ptr
	{
	public:
		// RAII
		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<T>&sp) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>&sp) = delete;
	private:
		T* _ptr;
	};
}

 2.3 std::shared_ptr

shared_ptr 是 C++11中引入的智能指针,shared_ptr 通过引用计数的方式解决智能指针的拷贝问题,也就是说 shared_ptr 支持拷贝。

shared_ptr 是一种共享式的智能指针,允许多个 shared_ptr 对象共享同一个对象。当最后一个shared_ptr 对象被销毁时,它所管理的对象才会被自动销毁。

// 头文件:
#include <memory> 

 文档介绍:shared_ptr

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

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

通过这种引用计数的方式就能支持多个对象一起管理某一个资源,也就是支持了智能指针的拷贝,并且只有当一个资源对应的引用计数减为 0 时才会释放资源,因此保证了同一个资源不会被释放多次。

测试代码:

// 引用计数支持多个拷贝管理同一个资源,最后一个析构对象释放资源
int main()
{
	shared_ptr<int> sp1(new int(1));
	shared_ptr<int> sp2(sp1);
	*sp1 = 2;
	*sp2 = 3;
	//use_count成员函数,用于获取当前对象管理的资源对应的引用计数
	cout << sp1.use_count() << endl;

	shared_ptr<int> sp3(new int(1));
	shared_ptr<int> sp4(new int(2));
	shared_ptr<int> sp5(new int(3));
	sp4 = sp3;
	sp5 = sp3;
	cout << sp3.use_count() << endl;
	return 0;
}

运行结果

2
3

调试查看

注意

  • shared_ptr 的内部的引用计数是线程安全的(++ 和 -- 是线程安全的)。
  • 如果多个线程同时拷贝同一个 shared_ptr 对象,不会有问题,因为 shared_ptr 的引用计数是线程安全的。
  • 如果多个线程同时修改同一个 shared_ptr 对象,不是线程安全的。
  • 如果多个线程同时读写 shared_ptr 指向的内存对象,不是线程安全的。

后序实验验证(C++专栏第43篇)。

shared_ptr 简单模拟实现

  1. 需要增加一个成员变量 count,表示智能指针对象管理的资源对应的引用计数。
  2. 在构造函数中申请资源,开辟在堆上,并初始化引用计数,设置为1,表示当前只有一个对象在管理这个资源。
  3. 在拷贝构造函数中,拷贝一次,需同时将该资源对应的引用计数 ++。
  4. 在拷贝赋值函数中,先将当前对象管理的资源对应的引用计数 --(如果减为0则需要释放),然后再与传入对象一起管理它管理的资源,同时需要将该资源对应的引用计数++。
  5. 在析构函数中,将管理资源对应的引用计数 --,如果减为0则需要将该资源释放。
namespace fy
{
    template <class T>
    class shared_ptr
    {
    public:
        // (1)RAII
        shared_ptr(T *ptr)
            : _ptr(ptr), _pCount(new int(1))
        {
        }
        ~shared_ptr()
        {
            if (--(*_pCount) == 0)
            {
                if (_ptr != nullptr)
                {
                    std::cout << "_ptr: " << _ptr << std::endl;
                    delete _ptr;
                    _ptr = nullptr;
                }
                delete _pCount;
                _pCount = nullptr;
            }
        }
        shared_ptr(const shared_ptr<T> &sp)
            : _ptr(sp._ptr), _pCount(sp._pCount)
        {
            ++(*_pCount);
        }

        shared_ptr<T> &operator=(shared_ptr<T> &sp)
        {
            if (_ptr != sp._ptr) // 管理同一块空间的对象之间无需进行赋值操作
            {
                if (--(*_pCount) == 0)
                {
                    std::cout << "operator= delete: " << _ptr << std::endl;
                    delete _ptr;
                    delete _pCount;
                }
                _ptr = sp._ptr;
                _pCount = sp._pCount;
                ++(*_pCount);
            }
            return *this;
        }
        // 获取引用计数
        int use_count()
        {
            return *_pCount;
        }
        // (2)可以像指针一样使用
        T &operator*()
        {
            return *_ptr;
        }
        T *operator->()
        {
            return _ptr;
        }

    private:
        T *_ptr;      // 管理的资源
        int *_pCount; // 管理的资源对应的引用计数
    };
}

测试运行

注意:shared_ptr 中的引用计数 count 不能单纯的定义成一个 int 类型的成员变量,因为这就意味着每个 shared_ptr 对象都有一个自己的 count 成员变量,而当多个对象要管理同一个资源时,这几个对象应该用到的是同一个引用计数。

2.4 shared_ptr 线程安全问题

上面模拟实现的 shared_ptr 还存在线程安全的问题,由于管理同一个资源的多个对象的引用计数是共享的,因此多个线程可能会同时对同一个引用计数进行自增或自减操作,而自增和自减操作都不是原子操作,因此需要通过加锁来对引用计数进行保护,否则就会导致线程安全问题。

所以需要对代码中的 ++ -- 操作进行加锁。

修改代码如下:

#pragma once
#include <iostream>
#include <memory>
#include <mutex>

namespace fy
{
    template <class T>
    class shared_ptr
    {
    public:
        // (1)RAII
        shared_ptr(T *ptr)
            : _ptr(ptr), _pCount(new int(1)), _mutex(new mutex)
        {
        }
        // 对++操作进行加锁
        void Add()
        {
            _mutex->lock();
            (*_pCount)++;
            _mutex->unlock();
        }
        // 对--操作进行加锁
        void Release()
        {
            _mutex->lock();
            bool flag = false;
            if (--(*_pCount) == 0) // 将管理的资源对应的引用计数--
            {
                if (_ptr != nullptr)
                {
                    cout << "delete: " << _ptr << endl;
                    delete _ptr;
                    delete _pCount;
                    _ptr = nullptr;
                    _pCount = nullptr;
                    flag = true;
                }
            }
            _mutex->unlock();
            if (flag == true) // 释放锁
            {
                delete _mutex;
            }
        }
        ~shared_ptr()
        {
            Release();
        }
        shared_ptr(const shared_ptr<T> &sp)
            : _ptr(sp._ptr), _pCount(sp._pCount), _mutex(sp._mutex)
        {
            Add();
        }

        shared_ptr<T> &operator=(shared_ptr<T> &sp)
        {
            if (_ptr != sp._ptr) // 管理同一块空间的对象之间无需进行赋值操作
            {
                Release();
                _ptr = sp._ptr;
                _pCount = sp._pCount;
                _mutex = sp._mutex;
                Add();
            }
            return *this;
        }
        // 获取引用计数
        int use_count()
        {
            return *_pCount;
        }
        // (2)可以像指针一样使用
        T &operator*()
        {
            return *_ptr;
        }
        T *operator->()
        {
            return _ptr;
        }

    private:
        T *_ptr;       // 管理的资源
        int *_pCount;  // 管理的资源对应的引用计数
        mutex *_mutex; // 管理的资源对应的互斥锁
    };
}
  • 在 Release 函数中,当引用计数被减为 0 时需要释放互斥锁资源,但不能在临界区中释放互斥锁,因为后面还需要进行解锁操作,因此代码中借助了一个 flag 变量,通过 flag 变量来判断解锁后释放需要释放互斥锁资源。
  • shared_ptr 只需要保证引用计数的线程安全问题,而不需要保证管理的资源的线程安全问题。
  • 指向堆上资源的线程安全问题是访问的人处理的,智能指针不管,也管不了。

智能指针的定制删除器

当智能指针对象的生命周期结束时,所有的智能指针默认都是以 delete 的方式将资源释放,这是不太合适的,因为智能指针并不是只管理以 new 方式申请到的内存空间,智能指针管理的也可能是以 new[] 的方式申请到的空间,或管理的是一个文件指针,以 new[] 的方式申请到的内存空间必须以 delete[] 的方式进行释放,而文件指针必须通过调用 fclose 函数进行释放。

定制删除器是通过仿函数、Lambda 表达式或函数指针来实现的,这里简单了解一下,定制删除器是实现很复杂。

2.5 std::weak_ptr

shared_ptr 存在一个致命的缺陷:循环引用,为了解决这个问题,产生了 weak_ptr。 

weak_ptr 是C++11中引入的智能指针,weak_ptr 不是用来管理资源的释放的,weak_ptr 是对 shared_ptr 的补充。

// 头文件:
#include <memory> 

文档介绍:weak_ptr

循环引用问题

shared_ptr 的循环引用问题在一些特定的场景下才会产生。

比如定义如下的结点类:在堆上新建了两个节点,并将这两个结点连接起来,最后再释放这两个节点。

struct ListNode
{
	ListNode* _next;
	ListNode* _prev;
	int _val;
	~ListNode(){ cout << "~ListNode()" << endl;}
};

int main()
{
	//新建节点
	ListNode* node1 = new ListNode;
	ListNode* node2 = new ListNode;

	node1->_next = node2;
	node2->_prev = node1;
	//释放
	delete node1;
	delete node2;
	return 0;
}

上述程序是没有问题的,两个结点都能够正确释放。为了防止程序中途返回或抛异常等原因导致结点未被释放,我们将这两个结点分别交给两个 shared_ptr 对象进行管理,这时为了让连接节点时的赋值操作能够执行,就需要把 ListNode 类中的 next 和 prev 成员变量的类型也改为 shared_ptr 类型。

struct ListNode
{
	std::shared_ptr<ListNode> _next;
	std::shared_ptr<ListNode> _prev;
	int _val;
	~ListNode() { cout << "~ListNode()" << endl; }
};

int main()
{
	//新建节点
	std::shared_ptr<ListNode> node1(new ListNode);
	std::shared_ptr<ListNode> node2(new ListNode);

	node1->_next = node2;
	node2->_prev = node1;
	return 0;
}

这时程序运行结束后两个结点都没有被释放,但如果去掉连接结点时的两句代码中的任意一句,那么这两个结点就都能够正确释放,根本原因就是因为这两句连接结点的代码导致了循环引用。

循环引用分析:

  1. node1 和 node2两 个智能指针对象指向两个节点,引用计数变成 1,我们不需要手动 delete。
  2. node1 的 _next 指向 node2,node2 的 _prev 指向 node1,引用计数变成 2。
  3. node1 和 node2 析构,引用计数减到 1,但是 _next 还指向下一个节点。但是 _prev还指向上一个节点。
  4. 也就是说 _next 析构了,node2 就释放了。
  5. 也就是说 _prev 析构了,node1 就释放了。
  6. 但是 _next 属于 node 的成员,node1 释放了, _next 才会析构,而 node1 由 _prev 管理, _prev 属于 node2 成员,所以这就叫循环引用,谁也不会释放。

weak_ptr 是 C++11中引入的智能指针,weak_ptr 不是用来管理资源的释放的,它主要是用来解决 shared_ptr 的循环引用问题的。

  • 原理:weak_ptr 支持用 shared_ptr 对象来构造 weak_ptr 对象,构造出来的 weak_ptr 对象与 shared_ptr 对象管理同一个资源,但不会增加这块资源对应的引用计数。

 在引用计数的场景下,把节点中的 _prev 和 _next 改成 weak_ptr 就可以了,原理就是:node1->_next = node2node2->_prev = node1时,weak_ptr 的 _next 和 _prev 不会增加 node1 和 node2 的引用计数。

修改代码如下:

struct ListNode
{
	std::weak_ptr<ListNode> _next;
	std::weak_ptr<ListNode> _prev;
	int _val;
	~ListNode() { cout << "~ListNode()" << endl; }
};

int main()
{
	//新建节点
	std::shared_ptr<ListNode> node1(new ListNode);
	std::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;
	return 0;
}

编译运行,资源正常释放

1
1
1
1
~ListNode()
~ListNode()

 weak_ptr 简单模拟实现

shared_ptr 还会提供一个 get 函数,用于获取其管理的资源,辅助 weak_ptr。

T* get() const
{
	return _ptr;
}

weak_ptr 模拟实现代码如下:

  1. weak_ptr 支持用 shared_ptr 对象拷贝构造 weak_ptr 对象,构造时获取 shared_ptr 对象管理的资源。
  2. weak_ptr 支持用 shared_ptr 对象拷贝赋值给 weak_ptr 对象,赋值时获取 shared_ptr 对象管理的资源。
template<class T>
	class weak_ptr
	{
	public:
		// RAII
		weak_ptr()
			:_ptr(nullptr)
		{}
		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())//weak_ptr支持用shared_ptr对象拷贝构造weak_ptr对象,构造时获取shared_ptr对象管理的资源。
		{}
		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();//weak_ptr支持用shared_ptr对象拷贝赋值给weak_ptr对象,赋值时获取shared_ptr对象管理的资源
			return *this;
		}
		// (2)可以像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};

三、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 中的实现的。
  5. boost 库是为C++语言标准库提供扩展的一些C++程序库的总称,boost 库社区建立的初衷之一就是为 C++ 的标准化工作提供可供参考的实现,比如在送审 C++ 标准库 TR1 中,就有十个 boost 库成为标准库的候选方案。

--------------- END ---------------

「 作者 」 枫叶先生
「 更新 」 2023.5.13
「 声明 」 余之才疏学浅,故所撰文疏漏难免,
          或有谬误或不准确之处,敬请读者批评指正。