智能指针
原理:
- 智能指针是一个类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏。
- 动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源
- 所以智能指针不用手动去释放内存
常用的智能指针:
1、shared_ptr:
实现原理:采用引用计数器的方法,允许多个智能指针指向同一个对象,每当多一个指针指向该对象时,指向该对象的所有智能指针内部的引用计数加1,每当减少一个智能指针指向对象时,引用计数会减1,当计数为0的时候会自动的释放动态分配的资源
- 智能指针将一个计数器与类指向的对象相关联,引用计数器跟踪共有多少个类对象共享同一指针
- 每次创建类的新对象时,初始化指针并将引用计数置为1
- 当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数
- 对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数
- 调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)
2、unique_ptr
- unique_ptr 指针指向的堆内存无法同其它 unique_ptr 共享
- 每个 unique_ptr 指针都独自拥有对其所指堆内存空间的所有权。
- 每个 unique_ptr 指针指向的堆内存空间的引用计数,都只能为 1,一旦该 unique_ptr 指针放弃对所指堆内存空间的所有权,则该空间会被立即释放回收。
- 所以unique_ptr不支持普通的拷贝和赋值操作,不能用在STL标准容器中
3、weak_ptr
- 该类型指针通常不单独使用(没有实际用处),只能和 shared_ptr 类型指针搭配使用
- 借助 weak_ptr 类型指针, 我们可以获取 shared_ptr 指针的一些状态信息,比如有多少指向相同的 shared_ptr 指针、shared_ptr 指针指向的堆内存是否已经被释放等等。
- 它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,也就是说,它只引用,不计数
- 如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存,内存也会被释放
- 所以weak_ptr不保证它指向的内存一定是有效的,在使用之前使用函数lock()检查weak_ptr是否为空指针
- expired函数:判断指针所指的内存空间是否被释放掉/指针是否为空/
- lock成员函数:返回一个shared_ptr类型的指针
智能指针的作用
- 方便管理堆内存。使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存。
- shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。
- 初始化。智能指针是个模板类,可以指定类型,传入指针通过构造函数初始化。
- 不能将指针直接赋值给一个智能指针,一个是类,一个是指针。例如std::shared_ptr
p4 = new int(1);的写法是错误的 - 同上
说说你了解的auto_ptr作用
- auto_ptr的出现,主要是为了解决“有异常抛出时发生内存泄漏”的问题;
- 抛出异常,将导致指针p所指向的空间得不到释放而导致内存泄漏;
- auto_ptr构造时取得某个对象的控制权,在析构时释放该对象。
- 不能直接将一般类型的指针赋值给auto_ptr类型的对象
- 由于auto_ptr对象析构时会删除它所拥有的指针,所以使用时避免多个auto_ptr对象管理同一个指针;
- 析构函数中删除对象用的是delete而不是delete[],所以auto_ptr不能管理数组
- T* get(),获得auto_ptr所拥有的指针;T* release(),释放auto_ptr的所有权,并将所有用的指针返回
智能指针的循环引用
循环引用
- 使用多个智能指针share_ptr时,出现了指针之间相互指向,从而形成环的情况,有点类似于死锁
- 这种情况下,智能指针往往不能正常调用对象的析构函数,从而造成内存泄漏
#include <iostream>
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:" << 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; //sp2的引用+1
sp2->_pPre = sp1; //sp1的引用+1
cout << "sp1.use_count:" << sp1.use_count() << endl;
cout << "sp2.use_count:" << sp2.use_count() << endl;
}
int main()
{
Funtest();
system("pause");
return 0;
}
//输出结果
//Node()
//Node()
//sp1.use_count:1
//sp2.use_count:1
//sp1.use_count:2
//sp2.use_count:2
- 应该尽量避免出现智能指针之前相互指向的情况,如果不可避免,可以使用使用弱指针——weak_ptr
- weak_ptr,它不增加引用计数,只要出了作用域就会自动析构。
智能指针出现循环引用怎么解决?
- 弱指针用于专门解决shared_ptr循环引用的问题
- weak_ptr不会修改引用计数,即其存在与否并不影响对象的引用计数器
手写实现智能指针类需要实现哪些函数?
- 一般用模板实现,模拟指针行为的同时还提供自动垃圾回收机制。
- 它会自动记录SmartPointer<T*>对象的引用计数,一旦T类型对象的引用计数为0,就释放该对象。
- 一个构造函数、拷贝构造函数、复制构造函数、析构函数、移动函数;
template<typename T>
class SharedPtr
{
public:
SharedPtr(T* ptr = NULL):_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 = NULL;
delete _pcount;
_pcount = NULL;
}
}
private:
T* _ptr;
int* _pcount;//指向引用计数的指针
};