一、智能指针概述
所谓智能指针就是智能/自动化的管理指针所指向的动态资源的释放。
从实现角度,智能指针就是RAII思想的一种实践;从使用角度,智能指针就像指针一样。这样像是在代码逻辑中因为引入异常而可能忘记释放申请的指针指向的内存的问题就可以很好地解决了。在遇到像定义了一个自定义类对象,又定义了一个指针指向它时会造成的析构多次的问题也可以通过智能指针很好地解决。
RAII(Resource Acquisition Is Initialization) 思想:资源分配即初始化,定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。
然而,智能指针的发展并不是一帆风顺的,早期的智能指针也是有很多缺陷。最早的智能指针是C98中的auto_ptr;后来针对auto_ptr的一些缺陷,出现了shared_ptr和weak_ptr,包含在boost第三方库中;再后来C11中继承完善了boost中的智能指针。
下面,我将从最开始的智能指针模拟实现,一步步学习智能指针!
二、auto_ptr
模拟实现:
#pragma once
template <class T>
class AutoPtr
{
public:
//实现构造函数保存资源
AutoPtr(T* ptr)
:_ptr(ptr)
{}
//实现析构函数释放资源
~AutoPtr()
{
printf("delete:%p\n", _ptr);
delete _ptr;
}
//实现让智能指针可以像指针一样使用
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
//拷贝构造函数和赋值的重载是通过管理权转移的方式实现的
AutoPtr(AutoPtr<T>& op)
:_ptr(op._ptr)
{
op._ptr = NULL;
}
AutoPtr<T>& operator=(AutoPtr<T>& op)
{
if (this != &op)
{
delete _ptr;
_ptr = op._ptr;
op._ptr = NULL;
}
return *this;
}
protected:
T* _ptr;
};
尝试使用一下,可以正常的像指针一样使用,也可以正常释放内存。
可是若进行拷贝构造:
可以发现只申请了一块空间,却释放了两次。这是因为这里使用的是管理权转移的方式进行的拷贝构造:
赋值运算符的重载也是一样的。
针对上面的问题,scoped_ptr简单粗暴地解决了这个问题。
三、scoped_ptr
模拟实现:
#pragma once
#include <stdio.h>
#include<iostream>
using namespace std;
template <class T>
class ScopedPtr
{
public:
//实现构造函数保存资源
ScopedPtr(T* ptr)
:_ptr(ptr)
{}
//实现析构函数释放资源
~ScopedPtr()
{
printf("delete:%p\n", _ptr);
if (_ptr)
delete _ptr;
}
//实现让智能指针可以像指针一样使用
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
private:
//简单粗暴地解决问题
ScopedPtr(ScopedPtr<T>& op);
ScopedPtr<T>& operator=(ScopedPtr<T>& op);
protected:
T* _ptr;
};
在这个版本的智能指针中,针对拷贝构造和赋值运算符的重载的问题,将这两个成员函数都只声明不定义,再把它们设置为私有的以防止函数调用者自己进行定义,这样简单粗暴地直接禁止使用拷贝构造确实能一定程度上避免以上问题,在大多数情况下这个版本的智能指针已经够用了。
那有没有方式能更好的解决问题呢?下面就是阐述shared_ptr了!
四、shared_ptr/weak_ptr
模拟实现:
#pragma once
#include <stdio.h>
#include <iostream>
using namespace std;
template <class T>
class SharedPtr
{
public:
SharedPtr(T* ptr)
:_ptr(ptr)
{
_pCount = new int(1);
}
~SharedPtr()
{
if (--(*_pCount) == 0)
{
printf("delete:%p\n", _ptr);
delete _ptr;
delete _pCount;
}
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
SharedPtr(SharedPtr<T>& op)
{
_ptr = op._ptr;
_pCount = op._pCount;
(*_pCount)++;
}
SharedPtr<T>& operator=(SharedPtr<T>& op)
{
if (_ptr != op._ptr)
{
if (--(*_pCount) == 0)
{
printf("delete:%p\n", _ptr);
delete _ptr;
delete _pCount;
}
_ptr = op._ptr;
_pCount = op._pCount;
(*_pCount)++;
}
return *this;
}
protected:
T* _ptr;
int* _pCount;
};
引入了引用计数,可以有效地避免重复析构的问题:
这时候我们再来看一个情景:
struct Node
{
SharedPtr<Node> _next;
SharedPtr<Node> _prev;
};
int main()
{
SharedPtr<Node> op1(new Node());
SharedPtr<Node> op2(new Node());
op1->_next = op2;
op2->_prev = op1;
return 0;
}

为了解决这个问题,又引入了weak_ptr,它和shared_ptr的区别是:weak_ptr没有引用计数。然后将weak_ptr声明为shared_ptr的友元类,这样weak_ptr就可以很方便的使用shared_ptr的成员了。
template <class T>
class WeakPtr
{
public:
WeakPtr(const SharedPtr<T>& sp)
:_ptr(sp._ptr)
{}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
//...
protected:
T* _ptr;
};
上面讨论的都是单个变量的指针,那么换成自定义对象数组会不会出现问题呢?
我们都知道自定义对象的数组的前面几个字节是用来存放数组元素的个数的,如果我们直接使用delete来释放,就会因为释放位置不正确造成问题,所以,就有了shared_array.有针对的对数组进行分开处理:
五、定制删除器
//定义定制删除器
template<class T>
struct Delete
{
//这个函数负责释放申请的空间
void operator()(T* ptr)
{
delete ptr;
}
};
//将delete ptr的定制删除器定义为缺省的定制删除器
template<class T, class D = Delete<T>>
class SharedPtr
{
friend class WeakPtr<T>;
public:
SharedPtr(T* ptr)
:_ptr(ptr)
,_pCount(new int(1))
{}
SharedPtr(T* ptr, D d)
:_ptr(ptr)
,_pCount(new int(1))
,_d(d)
{}
~SharedPtr()
{
if (--*_pCount == 0)
{
delete _pCount;
if(_ptr)
{
//调用_d的重载函数 operator()函数释放申请的空间;
_d(_ptr);
printf("delete:%p\n", _ptr);
}
}
}
operator void*()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
// sp2(sp1)
SharedPtr(const SharedPtr<T, D>& sp)
:_ptr(sp._ptr)
,_pCount(sp._pCount)
{
++(*_pCount);
}
// sp2 = sp3;
// sp2 = sp2;
SharedPtr<T, D>& operator=(const SharedPtr<T, D>& sp)
{
//if (this != &sp)
if(_ptr != sp._ptr)
{
if (--(*_pCount) == 0)
{
delete _pCount;
delete _ptr;
}
_ptr = sp._ptr;
_pCount = sp._pCount;
++(*_pCount);
}
return *this;
}
protected:
T* _ptr;
int* _pCount;
D _d;
};
template<class T>
struct DeleteArray
{
void operator()(T* ptr)
{
delete[] ptr;
}
};
struct Fclose
{
void operator()(FILE* ptr)
{
fclose(ptr);
}
};
void delete()
{
DeleteArray<string> da;
SharedPtr<string, DeleteArray<string>> sp1(new string[10], da);// 传入自己定义的定制删除器,会自动调用相应的delete方法
SharedPtr<FILE, Fclose> sp2(fopen("test.txt", "w"), Fclose());
}