智能指针:将对内存的管理交付给对象,当对象析构时就能够清理资源,有效的避免内存泄露问题。(因为原生态指针(T*)管理资源时,若用户忘记释放内存,则可能会导致资源泄露。)
- 下面介绍四种智能指针
- 头文件均为#include< memory>
一.auto_ptr智能指针
- C++98中经历过两次版本更新。
- C++11中保留第一版auto_ptr。
1.首先介绍智能指针的三大结构
- 1.RAII:利用对象生命周期来控制内存资源,构造时获取资源,析构时释放资源。
特点:不需要显式释放资源。对象所需资源在其生命周期内始终有效。 - 2.重载运算符 * (解引用) ->
- 3.拷贝构造方法/重载=运算符方法,所产生的浅拷贝问题。
2.auto_ptr第一版本模拟实现
解决浅拷贝:通过资源转移的方法,一份资源只能由一个指针管理。之前的指针设置为空。
#include<iostream>
using namespace std;
template <class T>
class auto_ptr1 {
public:
//1.RAII:
auto_ptr1(T* ptr=nullptr)
:_ptr(ptr)
{}
//2.重载* -> 运算符
T& operator *() {
return *_ptr;
}
T& operator ->() {
return _ptr;
}
//3.拷贝构造 并用资源转移的方法解决浅拷贝
auto_ptr1(auto_ptr1<T>& ap)
:_ptr(ap._ptr)
{
ap._ptr = nullptr;
}
auto_ptr1<T>& operator =(auto_ptr1<T>& ap) {
if (this != &ap) {
if (_ptr) {
delete _ptr;
}
_ptr = ap._ptr;
ap._ptr = nullptr;
}
_ptr = ap._ptr;
ap._ptr = nullptr;
}
~auto_ptr1() {
if (_ptr){
delete _ptr;
_ptr = nullptr;
}
}
private:
T* _ptr;
};
int main() {
auto_ptr<int> ap1(new int);
*ap1 = 10;
auto_ptr<int> ap2(ap1);
*ap2 = 20;
auto_ptr<int> ap3(new int);
*ap3 = 30;
auto_ptr<int> ap4;
ap4 = ap3;
*ap4 = 40;
return 0;
}
2.auto_ptr第二版本模拟实现
解决浅拷贝:通过资源管理权限的方法,设置一个bool类型变量,只有最后一个指针为true,拥有可以释放资源的权利。
#include<iostream>
#include<Windows.h>
using namespace std;
template <class T>
class auto_ptr1 {
public:
auto_ptr1(T* ptr=nullptr)
:_ptr(ptr)
,_owner(false)
{
if (_ptr) {
_owner = true;
}
}
T& operator *() {
return *_ptr;
}
T& operator ->() {
return _ptr;
}
auto_ptr1(auto_ptr1<T>& ap)
:_ptr(ap._ptr)
,_owner(ap._owner)
{
ap._owner = false;
}
auto_ptr1<T>& operator =(auto_ptr1<T>& ap) {
if (this != &ap) {
if (_ptr&&_owner) {
delete _ptr;
}
_ptr = ap._ptr;
_owner = ap._owner;
ap._owner = false;
}
return *this;
}
~auto_ptr1() {
if (_ptr&&_owner){
delete _ptr;
_ptr = nullptr;
}
}
private:
T* _ptr;
bool _owner;
};
int main() {
auto_ptr1<int> ap1(new int);
*ap1 = 10;
auto_ptr1<int> ap2(ap1);
*ap2 = 20;
auto_ptr1<int> ap3(new int);
*ap3 = 30;
auto_ptr1<int> ap4;
ap4 = ap3;
*ap4 = 40;
return 0;
}
3.C++11中,auto_ptr恢复到第一版本,因为上一版本有缺陷(可能成为野指针)。并提供了更优质的智能指针。
二.unique_ptr智能指针
1.原理:
- RAII
- 重载* ->
- 解决浅拷贝:不允许调用拷贝构造函数以及调用重载=运算符。即不允许进行拷贝。
2.unique_ptr模拟实现
#include<iostream>
using namespace std;
template <class T>
class unique_ptr1 {
public:
unique_ptr1(T* ptr=nullptr)
:_ptr(ptr)
{}
T& operator *() {
return *_ptr;
}
T& operator ->() {
return _ptr;
}
unique_ptr1(unique_ptr1<T>& ap)=delete //函数后面添加=delete 表示函数被删除,不允许被调用。
{}
//private :
// unique_ptr1(unique_ptr1<T>& ap){} 将函数私有化也可以。
unique_ptr1<T>& operator =(unique_ptr1<T>& ap) =delete
{}
~unique_ptr1() {
if (_ptr){
delete _ptr;
_ptr = nullptr;
}
}
private:
T* _ptr;
};
int main() {
unique_ptr1<int> ap1(new int);
*ap1 = 10;
return 0;
}
三.shared_ptr智能指针
1.原理
- RAII
- 重载* ->
- 解决浅拷贝:利用引用计数方式(记录资源被对象使用的个数)
2.代码
#include<iostream>
using namespace std;
template <class T>
class shared_ptr1 {
public:
shared_ptr1(T* ptr=nullptr)
:_ptr(ptr)
,_pcount(new int)
{
if (_ptr)
*_pcount = 1;
}
T& operator *() {
return *_ptr;
}
T& operator ->() {
return _ptr;
}
shared_ptr1(shared_ptr1<T>& ap)
:_ptr(ap._ptr)
, _pcount(ap._pcount)
{
if (_ptr)
(*_pcount)++;
}
shared_ptr1<T>& operator =(shared_ptr1<T>& ap)
{
if (this->_ptr != ap._ptr) {
if (_ptr&&--*_pcount == 0) {
delete _ptr;
_ptr = nullptr;
}
_ptr = ap._ptr;
_pcount = ap._pcount;
if (_ptr) {
(*_pcount)++;
}
}
return *this;
}
~shared_ptr1() {
if (_ptr&&--*_pcount==0){
delete _ptr;
_ptr = nullptr;
}
}
private:
T* _ptr;
int * _pcount;
};
int main() {
shared_ptr1<int> ap1(new int);
*ap1 = 10;
shared_ptr1<int> ap2(ap1);
*ap2 = 20;
shared_ptr1<int> ap3(new int);
*ap3 = 30;
shared_ptr1<int> ap4(new int);
ap4 = ap3;
*ap4 = 40;
return 0;
}
四.weak_ptr智能指针
1.说明:weak_ptr与shared_ptr原理相同,均采用引用计数的方法解决浅拷贝。但weak_ptr不可以单独使用,必须配套shared_ptr,为了解决shared_ptr产生的循环引用(相互引用,互相等待,不能调用析构函数,从而资源泄露)。-------》只需将结构内部循环引用的智能指针替换为weak_ptr即可。
2.原理:shared_ptr与weak_ptr都有一个独立的_pcount引用计数。所以可以避免循环引用导致的资源泄露。
3.代码
#include<iostream>
#include<memory>
using namespace std;
struct ListNode
{
int _data;
weak_ptr<ListNode> _prev;
weak_ptr<ListNode> _next; //只需将结构内部循环引用的智能指针替换为weak_ptr即可。
~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
node1->_next = node2;
node2->_prev = node1;
return 0;
}
五.智能指针注意事项
1.删除器:析构时,不同的构造方式需要调用不同的析构函数。
template<class T>
struct FreeFunc {
void operator()(T* ptr)
{
free(ptr);
}
};
template<class T>
struct DeleteArrayFunc {
void operator()(T* ptr)
{
delete[] ptr;
}
}
int main()
{
FreeFunc<int> freeFunc;
shared_ptr<int> sp1((int*)malloc(4), freeFunc);
DeleteArrayFunc<int> deleteArrayFunc;
shared_ptr<int> sp2((int*)malloc(4), deleteArrayFunc);
return 0;
}
2.线程安全问题:当遇到多线程问题时,资源可能不安全。
解决方法:加锁以及解锁。
#include <thread>
#include <mutex>
template <class T>
class SharedPtr
{
public:
SharedPtr(T* ptr = nullptr)
: _ptr(ptr)
, _pRefCount(new int(1))
, _pMutex(new mutex)
{}
~SharedPtr() {Release();}
SharedPtr(const SharedPtr<T>& sp)
: _ptr(sp._ptr)
, _pRefCount(sp._pRefCount)
, _pMutex(sp._pMutex)
{
AddRefCount();
}
// sp1 = sp2
SharedPtr<T>& operator=(const SharedPtr<T>& sp)
{
//if (this != &sp)
if (_ptr != sp._ptr)
{
// 释放管理的旧资源
Release();
// 共享管理新对象的资源,并增加引用计数
_ptr = sp._ptr;
_pRefCount = sp._pRefCount;
_pMutex = sp._pMutex;
AddRefCount();
}
return *this;
}
T& operator*() {return *_ptr;}
T* operator->() {return _ptr;}
int UseCount() {return *_pRefCount;}
T* Get() { return _ptr; }
void AddRefCount()
{
// 加锁或者使用加1的原子操作
_pMutex->lock();
++(*_pRefCount);
_pMutex->unlock();
}
private:
void Release()
{
bool deleteflag = false;
// 引用计数减1,如果减到0,则释放资源
_pMutex.lock();
if (--(*_pRefCount) == 0)
{
delete _ptr;
delete _pRefCount;
deleteflag = true;
}
_pMutex.unlock();
if(deleteflag == true)
delete _pMutex;
}
private:
int* _pRefCount; // 引用计数
T* _ptr; // 指向管理资源的指针
mutex* _pMutex; // 互斥锁
};