• 【C++】智能指针



    1. RAII

    💭智能指针是用以资源管理的一种工具。所谓资源就是,一旦用了它,将来就必须还给系统。如果不这样的话,会发生糟糕的事,如内存泄漏。C++程序中最常见的资源就是动态分配的内存(new/malloc申请堆上的内存,delete/free释放,如果不释放会导致内存泄漏)。谨慎地编写程序能让我们避免大部分不将资源还给系统的情况,但还是会有一些情况是难以避免的,举两个例子:

    ⭕情况1:func1()中,因为抛异常而未能执行语句delete[] pArray,资源未释放

    void func1()
    {
    	int* pArray = new int[10];
    
    	// to do something...
    	throw("error");
    
    	delete[] pArray;
    }
    
    int main()
    {
    	try
    	{
    		func1();
    	}
    	catch (const char* errmsg)
    	{
    	}
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    ⭕情况2:函数内多重回传路径

    void func1(int x)
    {
    	int* pArray = new int[10];
    
    	if (x > 0)
    	{
    		return;
    	}
    	else
    	{
    		std::cout << x << std::endl;
    	}
    
    	delete[] pArray;
    }
    
    int main()
    {
    	int x = 0;
    	func1(x);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    事实上,内存只是你管理的众多资源之一。其它资源如:打开的文件描述符(File Describtors)、互斥量(Mutex)、网络sockets等在使用后都需要归还给系统。而仅仅让程序员谨慎代码是很难完全规避内存泄漏的问题的。

    • RAII思想

      RAII(Resource Acquisiton Is Initialization),是一种利用对象的生命周期管理资源的方法,即“资源取得时机便是初始化时机”。每一笔资源在被获取到时,立刻被放入管理对象中。而管理对象运用析构函数确保资源被释放。也就是说,无论执行流以任何形式离开当前区块,一旦管理对象被销毁,生命周期结束,既调用管理对象的析构函数,而析构函数中包含了对象管理的资源的释放方法,这样就能保证资源被释放了。

    • RAII的优势

      1. 不需要显式地释放资源
      2. 对象管理的资源在其生命周期内有效

    而智能指针正是运用了RAII思想,对资源进行管理的一种对象。


    2. 智能指针

    智能指针,顾名思义,除了能够以RAII思想管理资源外,还要能够像原生指针一样使用,既通过*->访问管理资源,在C++类中可通过运算符重载实现。而智能指针真正的难点在于拷贝问题,不同的智能指针实现对拷贝问题有不同的解决方法。

    🔎下面介绍C++标准库中三种智能指针:

    头文件:#include

    auto_ptr

    C++98版本的库中就提供了auto_ptr的智能指针,下面演示auto_ptr的模拟实现。

    拷贝问题解决方法:资源管理权的交换

    在这里插入图片描述

    auto_ptr模拟实现

    namespace ckf
    {
    	template <class T>
    	class auto_ptr
    	{
    	public:
    		auto_ptr(T* ptr)
    			:_ptr(ptr)
    		{}
    
            // 拷贝构造,直接取ap的指针,并将ap指针设为空
    		auto_ptr(auto_ptr<T>& ap)
    			:_ptr(ap._ptr)
    		{
    			ap._ptr = nullptr;
    		}
    
            // 赋值重载
    		auto_ptr<T>& operator=(auto_ptr<T>& ap)
    		{
    			//交换资源管理权,即交换指针
    			std::swap(ap._ptr, _ptr);
    			return *this;
    		}
    
    		~auto_ptr()
    		{
                // 析构时释放资源
    			if (_ptr)
    				delete _ptr;
    		}
    
            // 像指针一样使用
    		T& operator*()
    		{
    			return *_ptr;
    		}
    
    		T* operator->()
    		{
    			return _ptr;
    		}
    
    	private:
    		T* _ptr; // 指针管理资源
    	};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    auto_ptr的缺陷:

    1. 多个auto_ptr无法指向同一资源的问题。(若多个auto_ptr指向同一资源,资源会被删除多次,这是错误的)
    2. 频繁交换资源管理权,会导致auto_ptr的指向不明确。

    综上所述,auto_ptr是一个失败的设计。


    unique_ptr

    C++11库中给出的更靠谱的unique_ptr

    拷贝问题解决方法:简单粗暴的禁止拷贝

    unique_ptr模拟实现

    namespace ckf
    {
    	template <class T>
    	class unique_ptr
    	{
    	public:
    		unique_ptr(T* ptr = nullptr)
    			:_ptr(ptr)
    		{}
            
            // 删除拷贝构造和赋值重载函数
    		unique_ptr(const unique_ptr& up) = delete;
    		unique_ptr<T>& operator=(const unique_ptr& up) = delete;
    
    		~unique_ptr()
    		{
    			if (_ptr)
    				delete _ptr;
    		}
    
    		T& operator*()
    		{
    			return *_ptr;
    		}
    
    		T* operator->()
    		{
    			return _ptr;
    		}
    
    	private:
    		T* _ptr;
    	};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    unique_ptr的缺陷:

    ​ 比起auto_ptr,指针的指向非常明确,但依然无法解决多个指针资源共享的问题。

    shared_ptr

    C++11中的shared_ptr完美解决了以上两个智能指针的问题。

    拷贝问题解决方法:引用计数

    在这里插入图片描述

    • shared_ptr模拟实现
    namespace ckf
    {
    	// 1. RAII
    	// 2. like pointer
    	// 3. copy
    
    	template <class T>
    	class shared_ptr
    	{
    		typedef shared_ptr<T> Self;
    	public:
    
    		T& operator*()
    		{
    			assert(_pdata);
    			return *_pdata;
    		}
    
    		T* operator->()
    		{
    			assert(_pdata);
    			return _pdata;
    		}
    
    
    		// constructor
    		shared_ptr(T* pdata = nullptr)
    			:_pdata(pdata)
    		{
    			if (_pdata)
    			{
    				_pcount = new int(1);
    				_pmtx = new std::mutex;
    			}
    			else
    			{
                    // 若指向资源为空,则计数器和互斥量也为空
    				_pcount = nullptr;
    				_pmtx = nullptr;
    			}
    		}
    
    		// destructor
    		~shared_ptr()
    		{
                // release先对计数器减1,再检查计数器是否为0,为0则释放资源
    			release();
    		}
    
    		// copy
    		shared_ptr(const Self& sp)
    			:_pdata(sp._pdata)
    			, _pcount(sp._pcount)
    			, _pmtx(sp._pmtx)
    		{
    			if(_pdata)
    				add_count();
    		}
    
    		// operator=
    		Self& operator=(const Self& sp)
    		{
    			// 相同就不用赋值了
    			if (_pdata != sp._pdata)
    			{
    				release();
    
    				_pdata = sp._pdata;
    				_pcount = sp._pcount;
    				_pmtx = sp._pmtx;
    				
    				if (_pdata)
    					add_count();
    			}
    
    			return *this;
    		}
    		
            // 获取计数器内容(指向为空返回0)
    		int use_count() const
    		{
    			if (_pdata)
    				return *_pcount;
    			else
    				return 0;
    		}
    
    		T* get() const
    		{
    			return _pdata;
    		}
    
    	private:
    
    		void release()
    		{
    			if (_pdata) // 若为空,什么也不做
    			{
                    // 访问计数器,需要加锁保护
    				_pmtx->lock();
    
    				bool deleteFlag = false;
    
    				// 计数器减到0,即释放资源
    				if (--(*_pcount) == 0)
    				{
    					delete _pdata;
    					delete _pcount;
    					deleteFlag = true;
    				}
    
    				_pmtx->unlock();
    				if (deleteFlag) // 资源释放,锁才释放
    					delete _pmtx;
    			}
    		}
    
    		void add_count()
    		{
                // 访问计数器,需要加锁保护
    			_pmtx->lock();
    
    			++(*_pcount);
    
    			_pmtx->unlock();
    		}
    
    		T* _pdata;
    		int* _pcount;
    		std::mutex* _pmtx;
    	};
    
    	// 多线程可能会使用同一个shared_ptr(包括它的拷贝)
    	// 因此
    	// 对于计数器的访问,应该由shared_ptr保证线程安全
    	// 对于sp指向数据,应该由用户自行保证数据安全
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • shared_ptr赋值的过程:

    在这里插入图片描述

    • shared_ptr线程安全的测试demo
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    
    class Date
    {
    public:
    	Date(int year = 2023, int month = 9, int day = 7)
    		:_year(year)
    		, _month(month)
    		, _day(day)
    	{}
    	~Date()
    	{
    		std::cout << "~Date()" << std::endl;
    	}
    
    	void getDate()
    	{
    		printf("%d年%d月%d日\n", _year, _month, _day);
    	}
    
    	int _year;
    	int _month;
    	int _day;
    };
    
    void SmartPtrTest(ckf::shared_ptr<Date>& sp, const int& n, std::mutex& mtx)
    {
    	// 测试ckf::shared_ptr的线程安全
    	for (int i = 0; i < n; i++)
    	{
    		// 这里两个线程不断拷贝sp和释放sp,既不断对sp计数器的++和--,最终保证sp计数器为1
    		ckf::shared_ptr<Date> copy(sp);
    
    		// 管理资源的安全由程序员保证
    		// 若对资源的访问是线程安全的
    		// 那么两个线程执行后三个值应该都是20000
    		mtx.lock();
    
    		copy->_year++;
    		copy->_month++;
    		copy->_day++;
    
    		mtx.unlock();
    	}
    
    	//这里出现2的原因:
    	//可能是另外一个线程正处于for循环中创建copy和销毁copy的间隙
    	//此时计数器为2
    	//线程调度到当前线程,use_count()打印的数就是2
    	//但是不影响最终计数器依旧为1
    	//std::cout << sp.get() << " " << sp.use_count() << std::endl;
    }
    
    void test_thread_safe()
    {
    	ckf::shared_ptr<Date> sp(new Date(0, 0, 0));
    	const int n = 100000;
    	std::mutex mtx;
    
    	// 观察多线程执行前后,sp计数器是否依然为1
    	cout << "before: " << sp.use_count() << endl;
    	sp->getDate();
    	cout << endl;
    
    	std::thread t1(SmartPtrTest, std::ref(sp), n, std::ref(mtx));
    	std::thread t2(SmartPtrTest, std::ref(sp), n, std::ref(mtx));
    	std::this_thread::sleep_for(std::chrono::seconds(1));
    
    	cout << "after: " << sp.use_count() << endl;
    	sp->getDate();
    	cout << endl;
    
    	t1.join();
    	t2.join();
    }
    
    int main()
    {
    	test_thread_safe();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    ⭕运行结果

    在这里插入图片描述

    • 删除器deleter

    有时候,单单的delete ptr无法满足释放资源的需求。就如:管理的资源是以new T[]形式开辟的,则需delete[]释放;又如:管理的资源是C语言打开文件的FILE结构体,那么需要fclose()函数释放。标准库中的shared_ptr配套了删除器,用于指定某种释放资源的方式。

    在这里插入图片描述

    使用方法如下:

    void test_deleter()
    {
        // 事实上deleter就是一个可调用对象,这里用了lambda表达式
    	std::shared_ptr<Date> sp1(new Date[10], [](Date* ptr) {delete[] ptr; });
    	std::shared_ptr<FILE> sp2(fopen("log.txt", "w"), [](FILE* fp) {fclose(fp); });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    shared_ptr with deleter

    namespace ckf
    {
    	// 1. RAII
    	// 2. like pointer
    	// 3. copy
    
    	template <class T>
    	class shared_ptr
    	{
    		typedef shared_ptr<T> Self;
    	public:
    
    		T& operator*()
    		{
    			assert(_pdata);
    			return *_pdata;
    		}
    
    		T* operator->()
    		{
    			assert(_pdata);
    			return _pdata;
    		}
    
    
    		// constructor
    		shared_ptr(T* pdata = nullptr)
    			:_pdata(pdata)
    		{
    			if (_pdata)
    			{
    				_pcount = new int(1);
    				_pmtx = new std::mutex;
    			}
    			else
    			{
    				_pcount = nullptr;
    				_pmtx = nullptr;
    			}
    		}
    		
            // 增加一个构造函数,可自动推导deleter类型
    		template <class D>
    		shared_ptr(T* pdata, D del)
    			:_pdata(pdata)
    			,_del(del)
    		{
    			if (_pdata)
    			{
    				_pcount = new int(1);
    				_pmtx = new std::mutex;
    			}
    			else
    			{
    				_pcount = nullptr;
    				_pmtx = nullptr;
    			}
    		}
    
    		// destructor
    		~shared_ptr()
    		{
    			release();
    		}
    		
            // deleter也要拷贝
    		// copy
    		shared_ptr(const Self& sp)
    			:_pdata(sp._pdata)
    			, _pcount(sp._pcount)
    			, _pmtx(sp._pmtx)
    			, _del(sp._del)
    		{
    			if(_pdata)
    				add_count();
    		}
    
    		// operator=
    		Self& operator=(const Self& sp)
    		{
    			// 相同就不用赋值了
    			if (_pdata != sp._pdata)
    			{
    				release();
    
    				_pdata = sp._pdata;
    				_pcount = sp._pcount;
    				_pmtx = sp._pmtx;
    				_del = sp._del;
    				
    				if (_pdata)
    					add_count();
    			}
    
    			return *this;
    		}
    
    		int use_count() const
    		{
    			if (_pdata)
    				return *_pcount;
    			else
    				return 0;
    		}
    
    		T* get() const
    		{
    			return _pdata;
    		}
    
    	private:
    
    		void release()
    		{
    			if (_pdata) // 若为空,什么也不做
    			{
    				_pmtx->lock();
    
    				bool deleteFlag = false;
    
    				// sp为空或者减计数到0,即释放空间
    				if (--(*_pcount) == 0)
    				{
    					//delete _pdata;
    					_del(_pdata);
    					delete _pcount;
    					deleteFlag = true;
    				}
    
    				_pmtx->unlock();
    				if (deleteFlag)
    					delete _pmtx;
    			}
    		}
    
    		void add_count()
    		{
    			_pmtx->lock();
    
    			++(*_pcount);
    
    			_pmtx->unlock();
    		}
    
    		T* _pdata;
    		int* _pcount;
    		std::mutex* _pmtx;
    		std::function<void(T*)> _del = [](T* ptr) {delete ptr; };
    	};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150

    shared_ptr的缺陷:

    shared_ptr是较为靠谱的一种智能指针,但是会有一个小问题——循环引用。看下面代码:

    struct Node
    {
    	ckf::shared_ptr<Node> _next;
    	ckf::shared_ptr<Node> _prev;
    
    	Node(int val) :_val(val)
    	{}
    
    	~Node()
    	{
    		std::cout << _val << "~Node()" << std::endl;
    	}
        
        int _val = 0;
    };
    
    void test_cycle_ref()
    {
    	ckf::shared_ptr<Node> n1(new Node(1));
    	ckf::shared_ptr<Node> n2(new Node(2));
    
    	n1->_next = n2;
    	n2->_prev = n1;
    }
    
    int main()
    {
        test_cycle_ref();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    📑演示图:

    在这里插入图片描述

    流程分析:

    1. n1和n2两个shared_ptr分别指向节点1和节点2,以RAII方式管理资源。此时两个节点的计数器都为1。
    2. 两个节点中的_next和_prev也是shared_ptr,节点1的_next指向节点2,节点2的_prev指向节点1。此时两个节点的计数器都增加为2。
    3. 函数执行完毕,先销毁n2——节点2的计数器减为1(节点1中的_next还指向节点2),再销毁n1——节点1的计数器减为1(节点2的_prev还指向节点1)。
    4. 此时陷入了僵局。只有_next析构了,节点2才能释放,只有_prev析构了,节点1才能释放。根据类的特性,节点1释放,_next才会析构,节点2释放,_prev才会析构。二者的释放条件依赖于彼此,谁也不释放,这就是循环引用。(节点1释放要2.prev析构->2.prev析构要节点2释放->节点2释放要1.next析构->1.next析构要节点1释放,形成闭环

    解决方法:

    C++11中引进了weak_ptr智能指针。该类型指针通常不单独使用,只能和 shared_ptr搭配使用来解决循环引用问题。

    1. weak_ptr并不参与资源的管理,只是作为shared_ptr的辅助,与shared_ptr指向相同的资源,不会修改所管理资源配套的计数器。
    2. weak_ptr没有重载*->运算符,这意味着weak_ptr只能指向内存空间,主要用于保存或赋予指针给shared_ptr,无法访问修改内存空间。
    3. weak_ptr也有引用计数,但只用于查看与该weak_ptr指向相同的shared_ptr的数量。引用计数为0时,weak_ptr处于过期状态。
    struct Node
    {
    	int _val = 0;
    	std::weak_ptr<Node> _next;
    	std::weak_ptr<Node> _prev;
    
    	Node(int val) :_val(val)
    	{}
    
    	~Node()
    	{
    		std::cout << _val << "~Node()" << std::endl;
    	}
    };
    
    void test_cycle_ref()
    {
    	std::shared_ptr<Node> n1(new Node(1));
    	std::shared_ptr<Node> n2(new Node(2));
    
        // weak_ptr可以接收shared_ptr的赋值,也能以shared_ptr进行初始化构造
    	n1->_next = n2; 
    	n2->_prev = n1;
    	
        // weak_ptr可以作为shared_ptr初始化构造的值,但是无法赋值给shared_ptr
    	std::shared_ptr<Node> n3(n1->_next); // 此时n3与n2指向相同
    
    	std::cout << n3.use_count() << std::endl;
    	std::cout << (n1->_next).use_count() << std::endl;
    }
    
    int main()
    {
        test_cycle_ref();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    运行结果:

    在这里插入图片描述


    3. 智能指针历史

    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中的实现的。

    Ending…

  • 相关阅读:
    在PHP8中对数组进行排序-PHP8知识详解
    STM32 HAL库DMA串口发送数据参考文章
    数据结构与算法之贪心&动态规划
    通关GO语言22 网络编程:Go 语言如何通过 RPC 实现跨平台服务?
    【FAQ】【ARK UI】HarmonyOS ETS 横竖屏适配
    分布式理论基础:CAP定理
    数字化时代,数据分析到底有什么意义
    垂起固定翼无人机基础知识,垂起固定翼无人机应用前景,垂起固定翼无人机优缺点分析
    Docker容器内用户与宿主机用户同名不同ID的问题
    Real-Time Rendering——8.2.3 Color Grading颜色分级
  • 原文地址:https://blog.csdn.net/C0631xjn_/article/details/132817707