一、为什么需要智能指针
如果一段程序中既申请了一段堆内存又打开好多文件,那么我们需要在程序任何可能退出去的地方进行手动关闭文件和释放堆内存,这样就程序为了关闭文件和释放堆内存导致程序看起来特别不和谐,所以提出来能否设计出我们只管使用自动释放指针呢?
二、智能指针设计原理
-
RAll特性
-
重载operator*和operator->,具有和指针一样的行为
RAll特性:考虑到类对象可以创建后自动释放,所以RAll利用这一特点。RAll就是利用对象的生命周期来控制资源技术。对象创建时获取资源,最后在析构函数释放资源。
RAll其他应用:可以实现加锁解锁功能。
注意:智能指针设计初衷就是帮用户管理资源,资源是用户传进来的,在智能指针内部不可以申请任何内存空间。
三、智能指针
头文件:#include<memory.h>
#include<memory.h>
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr = nullptr)
:_ptr(ptr)
{}
~AutoPtr()
{
delete _ptr;
_ptr = nullptr;
}
private:
T* _ptr;
};
int main()
{
AutoPtr<int> p1(new int);
AutoPtr<int> p2(p1);
return 0;
}
上述代码,存在一个致命问题,就是如果发生拷贝构造函数则是浅拷贝,为了解决浅拷贝首先我们想到的是深拷贝,但是深拷贝需要在类内部申请堆内存,违背了智能指针的思想(只是管理资源)。所以看下面标准库提供的各种智能指针怎么解决浅拷贝问题。
- std::auto_ptr(C++98)
- std::uniqued_ptr(C++11)
- std::shared_ptr(C++11)(更靠谱)
- std::auto_ptr(C++98)
auto_ptr是C++98设计,设计原理是 RAll + operator* + operator-> + 发生拷贝资源转移。解决浅拷贝方式是资源转移,也就是最新的指针指向堆内存,原来的指针指向nullptr。
#include<memory.h>
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr = nullptr)
:_ptr(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;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~AutoPtr()
{
delete _ptr;
_ptr = nullptr;
}
private:
T* _ptr;
};
int main()
{
AutoPtr<int> p1(new int);
AutoPtr<int> p2(p1);
*p2 = 1;
cout << *p2 << endl;
return 0;
}
auto_ptr缺点:对原来的对象解引用会导致程序崩溃。
改进:RAll + operator* + operator-> + 释放资源的权利转移(在维护一个权力标记),但是这种权利转移方法还有一个致命缺点就是全局对象和局部对象释放权力转移,出了局部作用域导致全局对象变为野指针。
C++11提供更靠谱的智能指针。
- std::unique_ptr
unique::ptr实现原理简单粗暴,直接禁止拷贝构造和赋值构造。禁止多个指针共享同一块内存。
template<class T>
class UniquePtr
{
public:
UniquePtr(T* ptr = nullptr)
:_ptr(ptr)
{}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~UniquePtr()
{
delete _ptr;
_ptr = nullptr;
}
//C++11禁止函数方法
UniquePtr(const UniquePtr<T> &ap) = delete;
UniquePtr<T>& operator=(const UniquePtr<T> &ap) = delete;
private:
//C++98禁止函数方法
//UniquePtr(const UniquePtr<T> &ap);
//UniquePtr<T>& operator=(const UniquePtr<T> &ap);
T* _ptr;
};
缺点:多个对象之间不可以共享资源。
- std::shared_ptr
该只智能指针支持拷贝构造和赋值构造。所以更靠谱。
实现原理:解决浅拷贝不只有深拷贝还有写时拷贝,所以shared_ptr利用写时拷贝中引用计数的方法实现多个对象之间共享资源。RAll + operator* + operator-> + 引用计数(线程安全加锁)
-
在shared_ptr中,给每一份资源维护一个技术,表示该资源被几个对象共享。
-
当对象出了作用域,调用析构函数,计数减1
-
当计数减1之后计数不为0,表示该资源还有被其他对象使用,不释放
-
当计数减1之后等于0,表示没有对象使用该资源,释放资源。
注意:此处使用指针类型计数方式,不是原子操作,所以在多线程情况下,不是线程安全的,所以需要对计数操作加锁。C++11引入了定义一个定义原子变量,所以不需要加锁(atomic)
缺点:存在循环引用的问题,造成内存泄漏。循环引用见下图和代码。
#include<iostream>
using namespace std;
#include<memory>
template<class T>
struct ListNode
{
T _data;
shared_ptr<ListNode<T>> _prev;
shared_ptr<ListNode<T>> _pnext;
ListNode(T data)
:_data(data)
, _prev(nullptr)
, _pnext(nullptr)
{}
~ListNode()
{
cout << this << endl;
}
};
int main()
{
shared_ptr<ListNode<int>> node1(new ListNode<int>(10));
shared_ptr<ListNode<int>> node2(new ListNode<int>(20));
cout << "node1:" << node1.use_count() << endl;
cout << "node2:" << node2.use_count() << endl;
node1->_pnext = node2;
node2->_prev = node1;
cout << endl;
cout << "node1:" << node1.use_count() << endl;
cout << "node2:" << node2.use_count() << endl;
return 0;
}
从上图运行结果可以看出,两个指针都没有释放。造成内存泄漏。
循环引用的解决方法:
- 当计数为1的时候,程序员手动解除循环引用这种方式。(实现起来容易错)
- 使用weak_ptr解决循环引用问题。
weak_ptr是专门用来解决循环引用问题,不可以独立的管理资源。
解决循环应用实现方法:吧结构体内部定义的shared_ptr换为weak_ptr。即可。
#include<iostream>
using namespace std;
#include<memory>
template<class T>
struct ListNode
{
T _data;
weak_ptr<ListNode<T>> _prev;
weak_ptr<ListNode<T>> _pnext;
ListNode(T data)
:_data(data)
{}
~ListNode()
{
cout << this << endl;
}
};
int main()
{
shared_ptr<ListNode<int>> node1(new ListNode<int>(10));
shared_ptr<ListNode<int>> node2(new ListNode<int>(20));
cout << "node1:" << node1.use_count() << endl;
cout << "node2:" << node2.use_count() << endl;
node1->_pnext = node2;
node2->_prev = node1;
cout << endl;
cout << "node1:" << node1.use_count() << endl;
cout << "node2:" << node2.use_count() << endl;
return 0;
}
可以看出程序结束后两个指针已经释放。
weak_ptr实现原理:主要作用就是使得shared_ptr内部的引用计数并不会加1,所以不会造成循环引用。
如果结构体内不管理了weak_ptr,那么shared_ptr内部的计数_RefCount指向的内存有两部分,一部分是use计数(默认为1),另外一个事weak计数(默认为1),所以发生循环引用的时候只是weak加1,变为2。又因为weak_ptr并不能够管理资源,所以可以准确释放。
四、仿函数删除器
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<cstdlib>
using namespace std;
struct File
{
void operator ()(void *ptr)
{
fclose((FILE*)ptr);
}
};
template<typename T>
struct DefaultDel
{
void operator()(T *ptr)
{
delete ptr;
}
};
template<typename T>
struct Free
{
void operator()(T *ptr)
{
free(ptr);
}
};
template<typename T>
struct DeleteArr
{
void operator()(T *ptr)
{
delete[] ptr;
}
};
template<typename T,typename D=DefaultDel<T>>
class SharedPtr
{
public:
SharedPtr(T* ptr)
:_ptr(ptr)
, _pCount(new int (1))
, _del(D())
{
}
~SharedPtr()
{
release();
}
SharedPtr(SharedPtr<T,D> & sp)
:_ptr(sp._ptr)
, _pCount(sp._pCount)
{
(*_pCount)++;
}
//现代写法
SharedPtr<T,D> operator = (SharedPtr<T,D> sp)
{
std::swap(_ptr, sp._ptr);
std::swap(_pCount, sp._pCount);
}
//传统写法
/*SharedPtr<T,D> operator = (SharedPtr<T,D>& sp)
{
if (this != &sp)
{
release();
_ptr = sp._ptr;
_pCount = sp._pCount;
(*_pCount)++;
}
}*/
T* operator ->()
{
return _ptr;
}
T& operator *()
{
return *_ptr;
}
private:
void release()
{
if (--(*_pCount) == 0)
{
_del(_ptr);
delete _pCount;
_ptr = NULL;
_pCount = NULL;
}
}
private:
T* _ptr;
int* _pCount;
D _del;//所给的删除器类型
};
void test1()
{
SharedPtr<FILE, File> sp(fopen("test.txt", "w"));
SharedPtr<int> sp2(new int(1));
}
int main()
{
test1();
system("pause");
return 0;
}