目录
RAII
auto_ptr
unique_ptr
weak_ptr
删除器
智能指针的出现主要是针对程序的资源泄露问题而产生的。
RAII
RAII(Resource Acquisition Is Initialization)是种利用对象生命周期来控制程序资源的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处: 1、不需要显式地释放资源。 2、采用这种方式,对象所需的资源在其生命期内始终保持有效。
智能指针还应具有指针的行为,因此还需要重载*和->
template<class T>
class SmartPtr{
public:
SmartPtr(T* ptr)
:_ptr(ptr)
{}
~SmartPtr(){
cout << "delete:" << _ptr << endl;
delete _ptr;
}
T& operator*(){
return *_ptr;
}
T* operator->(){
return _ptr;
}
private:
T* _ptr;
};
总结只能指针的原理:1、具有RAII特性2、重载operation*和operation->,具有指针一样的行为
auto_ptr
C++98版本的库中就提供了auto_ptr的智能指针。auto_ptr的实现原理:管理权转移的思想。下边是对auto_ptr的模拟
template<class T>
class auto_ptr {
public:
auto_ptr(T* ptr)
:_ptr(ptr)
{}
auto_ptr( auto_ptr<T>& ap):_ptr(ap._ptr) {
ap._ptr = nullptr;
}
auto_ptr<T>& operator=(auto_ptr<T>& ap) {
if (this != &ap) {
if (_ptr) {
delete _ptr;
}
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
~auto_ptr() {
cout << "delete:" << _ptr << endl;
delete _ptr;
}
T& operator*() {
return *_ptr;
}
T* operator->() {
return _ptr;
}
private:
T* _ptr;
};
上边的ap1中的资源经过拷贝构造后,ap1的资源转移到了ap2去管理了。
unique_ptr
unique_ptr的实现原理:简单粗暴的防拷贝。下边是对unique_ptr的模拟
template<class T>
class unique_ptr {
public:
unique_ptr(T* ptr)
:_ptr(ptr)
{}
//防拷贝 C++11
unique_ptr(unique_ptr<T>& ap) = delete;
unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;
~unique_ptr() {
cout << "delete:" << _ptr << endl;
delete _ptr;
}
T& operator*() {
return *_ptr;
}
T* operator->() {
return _ptr;
}
private:
T* _ptr;
};
将拷贝构造和赋值重载进行了禁用。
shared_ptr
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。下边是对shared_ptr的模拟
template<class T>
struct Delete {
void operator()(T* ptr) {
cout << "delete" << endl;
delete ptr;
}
};
template<class T, class D = Delete<T>>
class shared_ptr{
public:
shared_ptr(T* ptr = nullptr)
: _ptr(ptr)
, _pCount(new int(1))
{}
void Release(){
if (--(*_pCount) == 0){
//cout << "Delete:" << _ptr << endl;
//delete _ptr;
D()(_ptr);
delete _pCount;
}
}
~shared_ptr(){
Release();
}
// sp1(sp2)
shared_ptr(const shared_ptr<T>& sp)
: _ptr(sp._ptr)
, _pCount(sp._pCount){
(*_pCount)++;
}
// sp1 = sp5
// sp1 = sp1
shared_ptr<T>& operator=(const shared_ptr<T>& sp){
//if (this == &sp)
if (_ptr == sp._ptr){
return *this;
}
// 减减被赋值对象的计数,如果是最后一个对象,要释放资源
Release();
// 共管新资源,++计数
_ptr = sp._ptr;
_pCount = sp._pCount;
(*_pCount)++;
return *this;
}
T& operator*(){
return *_ptr;
}
T* operator->(){
return _ptr;
}
int use_count() {
return *_pCount;
}
T* get() const{
return _ptr;
}
private:
T* _ptr;
int* _pCount;// 引用计数
};
我们可以观察到sp1的资源由2个对象(sp1、sp2)共享。sp3的资源仅由自己管理。sp4的资源由2个对象(sp4、sp5)共享。
shared_ptr的循环引用
struct Node{
int _val;
hzp::shared_ptr<Node> _next;
hzp::shared_ptr<Node> _prev;
~Node(){
cout << "~Node" << endl;
}
};
void test_weak_ptr(){
std::shared_ptr<Node> n1(new Node);
std::shared_ptr<Node> n2(new Node);
n1->_next = n2;
n2->_prev = n1;
}
上边的代码逻辑可以参考下图
待函数结束时,n2先析构,n1再析构,引用计数各自减至1。但是函数结束了,按理应该销毁,引用计数应该为0。但是左边中的_next指向的右边,右边的_prev指向的左边。二者谁也不让谁,就导致了循环引用的问题。
weak_ptr
shared_ptr的循环引用问题的解决引出了weak_ptr。weak_ptr和特点是不增加引用计数,是一个辅助型的智能指针。
_next和_prev是weak_ptr的时候,其不参与资源释放管理,可以访问和修改资源,但是不增加计数,就不存在循环引用问题了。
下边是weak_ptr的模拟
// 辅助型智能指针,使命配合解决shared_ptr循环引用问题
template<class T>
class weak_ptr{
public:
weak_ptr()
:_ptr(nullptr)
{}
weak_ptr(const shared_ptr<T>& sp)
:_ptr(sp.get())
{}
weak_ptr(const weak_ptr<T>& wp)
:_ptr(wp._ptr)
{}
weak_ptr<T>& operator=(const shared_ptr<T>& sp){
_ptr = sp.get();
return *this;
}
T& operator*(){
return *_ptr;
}
T* operator->(){
return _ptr;
}
public:
T* _ptr;
};
删除器
首先我们需要知道new和malloc的关系,delete和free的关系。比如new int[5],其中new的过程中包括malloc+5次构造函数,delete[]的过程包括free+5次析构函数。
但是new的过程中,编译器能通过代码中的[ ]得知该使用几次构造函数,而对应的delete[]是如何知道使用几次析构函数的呢?
下边可以参考vs平台下的机制。
delete[]的ptr的前4个字节中存放了应该使用几次析构函数的次数。也就是在delete[]的时候,ptr并不是delete的起始位置,而是(char*)ptr-4的位置。 如果只是delete的话,起始位置就是ptr。
void test_deleter(){
// 仿函数对象
std::shared_ptr<Node> n1(new Node[5], DeleteArray<Node>());
std::shared_ptr<Node> n2(new Node);
std::shared_ptr<int> n3(new int[5], DeleteArray<int>());
std::shared_ptr<int> n4((int*)malloc(sizeof(12)), Free<int>());
}