1、智能指针的原理

智能指针是一个类,可以在这个类的构造函数中传入一个普通指针,在析构函数中释放传入的指针。


智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。


C++里面的四个智能指针: auto_ptr, unique_ptr,shared_ptr, weak_ptr 其中后三个是C++11支持,并且第一个已经被C++11弃用。

用的比较多的是shared_ptr共享指针

2、简单了解一下四种智能指针

按照发展的时间进行展开

2.1 auto_ptr

  1. ​auto_ptr​​以前是用在C98中,C++11被抛弃,头文件一般用来作为独占指针
  2. ​auto_ptr​​被赋值或者拷贝后,失去对原指针的管理
  3. ​auto_ptr​不能管理数组指针,因为​​auto_ptr​​的内部实现中,析构函数中删除对象使用delete而不是delete[],释放内存的时候仅释放了数组的第一个元素的空间,会造成内存泄漏。
  4. auto_ptr不能作为容器对象,因为STL容器中的元素经常要支持拷贝,赋值等操作。

举个栗子,会存在隐藏内存泄露问题,有拷贝语义,拷贝后源对象变得无效,这可能引发很严重的问题。

auto_ptr<int> p1(new int(5));
auto_ptr<int> p2 = p1; // 编译通过,但是后面使用p1会出现内存泄漏问题

2.2 unique_ptr:

  1. C++11中用来替代auto_ptr
  2. 拷贝构造和赋值运算符被禁用,不能进行拷贝构造和赋值运算
  3. 虽然禁用了拷贝构造和赋值运算符,但unique_ptr可以作为返回值,用于从某个函数中返回动态申请内存的所有权,本质上是移动拷贝,就是使用std:move()函数,将所有权转移。
unique_ptr<int> p1(new int(5));
unique_ptr<int> p2 = p1; // 编译会出错
unique_ptr<int> p3 = std::move(p1); // 转移所有权, 现在那块内存归p3

2.3 share_ptr:​????​ ​????​ ​????​


目前使用得比较多的智能指针,要熟悉其原理


  1. 多个指针可以指向相同的对象,调用release()计数-1,计数0时资源释放
  2. use_count()查计数
  3. reset()放弃内部所有权
  4. share_ptr多次引用同一数据会导致内存多次释放
  5. 循环引用会导致死锁;
  6. 引用计数不是原子操作。

2.3.1手写智能指针类需要实现哪些函数?以及注意哪些细节

  1. 采用模板函数区设计,私有成员为指针以及指针技术;
  2. 带参的构造函数中负责引用计数的自增;
  3. 析构函数负责引用计数的减1和释放内存;
  4. 定义拷贝构造函数和赋值函数以及移动函数

2.3.2简单版的实现方法

#include<iostream>
using namespace std;

template<typename T>
class SharedPtr
{
private:
T* _ptr;
int* _pcount;// 指向引用计数的指针

public:
SharedPtr(T* ptr = nullptr):_ptr(ptr), _pcount(new int(1))
{}

SharedPtr(const SharedPtr& s):_ptr(s._ptr), _pcount(s._pcount){
(*_pcount)++;
}

// 赋值构造函数
SharedPtr<T>& operator=(const SharedPtr& s){
if(this != &s)
{
if(--(*(this->_pcount)) == 0)
{
delete this->_ptr;
delete this->_pcount;
}
_ptr = s._ptr;
_pcount = s._pcount;
*(_pcount)++;
}
return *this;
}

T& operator*()
{
return *(this->ptr);
}
T* operator->()
{
return this->ptr;
}
~SharedPtr()
{
--(*(this->_pcount ));
if(*(this->_pcount == 0)){
delete _ptr;
_ptr = nullptr;
delete _pcount;
_pcount = nullptr;
}
}
};

int main(){
int b = 20;
int *p = &b;
SharedPtr* sharep = new SharedPtr(p);
}

但是shree_ptr还是无法解决一些问题

2.3.3智能指针的循环使用


循环引用是指使用多个智能指针share_ptr时,出现了指针之间相互指向,从而形成环的情况,有点类似于死锁的情况,这种情况下,智能指针往往不能正常调用对象的析构函数,从而造成内存泄漏。


//智能指针的循环使用
#include<iostream>
#include<memory>
using namespace std;

template<typename T>
class Node
{
public:
Node(const T& value):_pPre(NULL),_pNext(NULL),_value(value){
cout<<"Node()"<<endl;
}
~Node(){
cout<<"~Node()"<<endl;
cout<<"this:"<<endl;
}

shared_ptr<Node<T>>_pPre;
shared_ptr<Node<T>> _pNext;
T _value;
};

void Funtest()
{
shared_ptr<Node<int>> sp1(new Node<int>(1));
shared_ptr<Node<int>> sp2(new Node<int>(2));

cout << "sp1.use_count:" << sp1.use_count() << endl;
cout << "sp2.use_count:" << sp2.use_count() << endl;

sp1->_pNext =sp2;//sp1的引用+1
sp2->_pPre = sp1;//sp2的引用+1

cout<< "sp1.use_count: "<< sp1.use_count() <<endl;
cout<< "sp2.use_count: "<< sp2.use_count() <<endl;

}

int main()
{
Funtest();
return 0;
}

C++11 智能指针 知识整理笔记_c++

只有当sp1的计数为0时才析构,而上述情况造成了一个僵局,那就是当析构sp2时候,由于sp2->pre = sp1,sp1还在用,而sp1又会用sp2,所以sp2.use_count减减之后为1,不释放,sp1也是相同的道理,由于sp1的空间sp2还在使用中,所以sp1.use_count减减之后为1,也不释放。sp1等着sp2先释放,sp2等着sp1先释放,二者互不相让,导致最终都没能释放,内存泄漏。


在实际编程过程中,应该尽量避免出现智能指针之前相互指向的情况,如果不可避免,可以使用使用弱指针——weak_ptr,它不增加引用计数,只要出了作用域就会自动析构。


为了解决上述存在的问题,需要引入弱智能指针

4.weak_ptr

1.解决两个share_ptr互相引用产生死锁,计数永远降不到0,没办法进行资源释放,造成内存泄漏的问题。

2.使用时配合share_ptr使用,把其中一个share_ptr更换为weak_ptr。


weak_ptr用于避免shared_ptr相互指向产生的环形结构,造成的内存泄漏。weak_ptr count是弱引用个数;弱引用个数不影响shared count和对象本身,shared count为0时则直接销毁。


3 参考学习资料:

  1. ​阿秀笔记​
  2. ​C++智能指针​
  3. ​C++智能指针​
  4. ​牛客网智能指针笔记​