智能指针原理
文章目录
智能指针
为什么需要智能指针?
在C++中new一个对象以后,需要手动的释放这块空间,这样会触发很多情况,导致不能被释放。
int mian()
{
pair<string,string>* p1 = new pair<string,string>;
func();
delete p1;
return 0;
}
假如这里的func()函数抛出异常,那么delete不能够执行到位,会造成内存泄漏。
RAII
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:不需要显式地释放资源。采用这种方式,对象所需的资源在其生命期内始终保持有效。
第一个版本的智能指针
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
using namespace std;
template <class T>
class SmartPtr
{
public:
SmartPtr(T* ptr)
:_ptr(ptr)
{
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
int main()
{
SmartPtr<string> p1(new string("xxxxx"));
SmartPtr<string> p2(new string("yyyyyy"));
p1 = p2;
return 0;
}
这个情况下,p1的指针指向p2的空间,造成p1的空间泄露,p2指向的空间不仅有p2指向,现在还有p1指向,会导致free两次。
auto_ptr,管理权转移
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<memory>
using namespace std;
class A
{
public:
A(int a = 1)
:_a(a)
{
cout << "A(int a = 1)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
//private:
int _a;
};
int main()
{
auto_ptr<A> p1(new A(1));
auto_ptr<A> p2(new A(2));
//auto_ptr 会将管理权转移,拷贝时,会把拷贝对象的资源管理权转移给被拷贝的对象
//导致被拷贝对象悬空,访问就会出现问题
auto_ptr<A> p3 = p1; //这里的p1就悬空了
//崩溃
p1->_a++;
p3->_a++;
return 0;
}
unique_ptr,不允许拷贝
template<class T>
class unique_ptr
{
public:
unique_ptr(T* ptr)
:_ptr(ptr)
{
}
//防止拷贝
unique_ptr(unique_ptr<T>& ptr) = delete;
unique_ptr<T> operator=(unique_ptr<T>& ptr) = delete;
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
shared_ptr,引用计数
#include <iostream>
using namespace std;
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr)
: _ptr(ptr)
, _pcount(new int(1))
{
}
~shared_ptr()
{
if (--(*_pcount) == 0)
{
std::cout << "delete " << _ptr << std::endl;
delete _ptr;
delete _pcount;
}
}
shared_ptr(const shared_ptr<T>& ptr)
: _ptr(ptr._ptr)//同一个类的实例可以互相访问私有变量
, _pcount(ptr._pcount)
{
(*_pcount)++;
}
shared_ptr<T>& operator=(const shared_ptr<T>& ptr)
{
if (this == &ptr)
{
return *this;
}
if (--(*_pcount) == 0)
{
delete _ptr;
delete _pcount;
}
_ptr = ptr._ptr;
_pcount = ptr._pcount;
++(*_pcount);
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get()
{
return _ptr;
}
private:
T* _ptr;
int* _pcount;
};
循环引用
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<memory>
using namespace std;
class A
{
public:
A(int a = 1)
:_a(a)
{
cout << "A(int a = 1)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
struct Node
{
A _val;
shared_ptr<Node> prev;
shared_ptr<Node> next;
};
int main()
{
shared_ptr<Node> sp1(new Node);
shared_ptr<Node> sp2(new Node);
sp1->next = sp2;
sp2->prev = sp1;
return 0;
}
-
Node
结构体包含了两个sh ared_ptr<Node>
成员:prev
和next
,它们分别指向前一个和后一个节点。 -
shared_ptr
是一种智能指针,使用引用计数来管理对象的生命周期。当一个新的shared_ptr
指向某个对象时,该对象的引用计数增加;当一个shared_ptr
被销毁或指向其他对象时,引用计数减少。 -
sp1
和sp2
分别是两个shared_ptr<Node>
对象,指向两个不同的Node
实例。 -
然后,
sp1->next
被设置为sp2
,sp2->prev
被设置为sp1
,这就导致了sp1
和sp2
互相引用。
循环引用的产生
-
sp1
引用sp2
:- 当
sp1->next = sp2
执行后,sp1
中的next
指向了sp2
,这意味着sp2
的引用计数增加了 1。
- 当
-
sp2
引用sp1
:- 当
sp2->prev = sp1
执行后,sp2
中的prev
指向了sp1
,这意味着sp1
的引用计数也增加了 1。
- 当
由于 sp1
和 sp2
互相引用对方,形成了一个循环引用。
循环引用的后果
当 main
函数结束时,sp1
和 sp2
都应该被销毁,然而,由于它们互相引用,两个 shared_ptr
的引用计数都不会降到 0,因此内存不会被释放。
sp1
持有一个指向sp2
的shared_ptr
,这使得sp2
的引用计数永远不会降到 0。- 同样,
sp2
持有一个指向sp1
的shared_ptr
,使得sp1
的引用计数也永远不会降到 0。
这种情况下,即使 sp1
和 sp2
超出了作用域并且 main
函数结束了,它们所指向的 Node
对象依然无法被销毁,导致内存泄漏。
weak_ptr,不是RAII智能指针,专门解决循环引用问题
原理:不增加引用计数,不参与资源的释放管理,可以访问资源。
template<class T>
class weak_ptr
{
public:
weak_ptr(T* ptr = nullptr)
:_ptr(ptr)
{
}
weak_ptr(const shared_ptr<T>& sp)
:_ptr(sp.get())
{
}
weak_ptr<T>& operator=(const shared_ptr<T>& sp)
{
_ptr = sp.get();
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
原文地址:https://blog.csdn.net/nongcha_ice/article/details/142705462
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!