题目
- 为什么要定义智能指针?
- 智能指针的原理
- RALL
- 智能指针的使用
- std::auto_ptr
- std::unique_ptr
- std::shared_ptr
为什么要定义智能指针?
之前定义指针申请内存空间使用后要进行delete进行资源释放,但是如果在资源释放之前就抛出异常,则不会进行正常的资源释放,造成资源泄露的问题。长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
例如:
void MemoryLeaks()
{
// 1.内存申请
int* p1 = new int[10];
throw;delete[] p1;
}
因此通过定义智能指针,利用对象的生命周期来控制程序资源,在生命周期结束时自动释放资源。
智能指针的原理
RALL
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。这种做法有两大好处:
- 不需要显式地释放资源。
- 采用这种方式,对象所需的资源在其生命期内始终保持有效。
//智能指针:
//1.实现RALL思想
//2.实现指针功能:*,->
template <class T>
class AutoPtr
{
public://RALL//构造函数获取资源的管理权AutoPtr(T* ptr):_ptr(ptr){}//析构中释放资源~AutoPtr(){if (_ptr)delete _ptr;}T* operator->(){return _ptr;}T& operator*(){return *_ptr;}//管理权转移AutoPtr(AutoPtr<T>& ap):_ptr(ap.ptr){ap._ptr = nullptr;}AutoPtr<T>& operator=(AutoPtr<T>& ap){if (this != &ap){if (_ptr)delete _ptr;//管理权转移_ptr = ap._ptr;ap._ptr = nullptr;}return *this;}
private:T* _ptr;
};class A
{
public:int _a = 10;
};void test()
{错误写法:该写法容易造成资源二次释放//int* ptr = new int;//SmartPtr<int> sp(ptr);//SmartPtr<int> sp2(ptr);//正确写法:AutoPtr<int> sp1(new int);//SmartPtr<int> sp2((int*)malloc(sizeof(int)));AutoPtr<A> sp2(new A);//智能指针,编译器调用 析构自动释放内存,不存在内存泄露的问题*sp1 = 10;sp2->_a = 100;(*sp2)._a = 1000;//普通指针,手动释放内存,存在内存泄漏的问题int* p = new int;A* pa = new A;*p = 1;pa->_a = 5;(*pa)._a = 55;delete p;delete pa;
}
智能指针的使用
std::auto_ptr
C++98版本的库中就提供了auto_ptr的智能指针。
auto_ptr的实现原理:管理权转移的思想。
拷贝时直接将新指针指向被拷贝对象资源,被拷贝对象指针悬空。
auto_ptr<int> sp1(new int);auto_ptr<int> sp2(sp1); // 管理权转移//sp1悬空
std::unique_ptr
unique_ptr的实现原理:简单粗暴的防拷贝。
//unique_ptr:防拷贝unique_ptr<int> up(new int);//unique_ptr的拷贝构造为删除函数//unique_ptr<int> up2(up);unique_ptr<int> up3(new int);//unique_ptr赋值运算符为删除函数//up3 = up;
std::shared_ptr
C++11中开始提供更靠谱的并且支持拷贝的shared_ptr.
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
- shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
- 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
- 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
- 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
循环引用分析:
- node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动
delete。 - node1的_next指向node2,node2的_prev指向node1,引用计数变成2。
- node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。
- 也就是说_next析构了,node2就释放了。
- 也就是说_prev析构了,node1就释放了。
- 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。
struct ListNode
{shared_ptr<ListNode> _next;shared_ptr<ListNode> _prev;int _data;~ListNode(){cout << "~ListNode" << endl;}
};
void test1()
{shared_ptr<ListNode> n1(new ListNode);shared_ptr<ListNode> n2(new ListNode);cout << n1.use_count() << endl;cout << n2.use_count() << endl;n1->_next = n2;n2->_prev = n1;cout << n1.use_count() << endl;cout << n2.use_count() << endl;
}
使用weak_ptr可以在拷贝时不进行计数+1操作,因此可以正常调用析构,进行资源释放。
struct ListNode
{weak_ptr<ListNode> _next;weak_ptr<ListNode> _prev;int _data;~ListNode(){cout << "~ListNode" << endl;}
};
void test1()
{shared_ptr<ListNode> n1(new ListNode);shared_ptr<ListNode> n2(new ListNode);cout << n1.use_count() << endl;cout << n2.use_count() << endl;n1->_next = n2;n2->_prev = n1;cout << n1.use_count() << endl;cout << n2.use_count() << endl;
}