1.资源泄漏问题
C++并没有垃圾回收机制(gc),这使得我们使用new或者malloc等开出空间后,需要释放资源。
但是当我们忘记释放资源,或者有异常安全问题时。就会导致资源没有被回收,我们又不能继续使用这块资源,终止导致资源泄漏问题
2.避免资源泄漏问题的方法
2.1我们可以良好的设计规范,申请资源结束后释放资源;遇到异常安全问题,我们可以使用异常重抛出解决资源泄漏。
2.2 利用RAII的思想或者智能指针来管理资源。
3.智能指针
3.1RAII
RAII计数是一种利用对象生命周期来控制程序资源的一种技术。其包括①在构造对象时获取资源②在析构对象时,释放资源。
它的好处在于,我们不需要显示的手动释放资源,在对象生命周期结束后,会自动调用其析构函数,达到释放资源的目的。
3.2智能指针
智能指针就是利用上述RAII的思路设计的。当然我们还需要加入operator->和operator*,才能向指针一样使用它。
// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr)
: _ptr(ptr)
{}
~SmartPtr()
{
if(_ptr)
delete _ptr;
}
T& operator*() {return *_ptr;}
T* operator->() {return _ptr;}
private:
T* _ptr;
};
但是以上的智能指针存在一个问题,当我们将一个指针同时交给两个不同的对象管理时,就会将这块指针管理的空间delete掉两次,会报错。接下来就是C++的解决方案历程了
3.3 auto_ptr(C++98)
在c++98的auto_ptr的设计中就提出了管理权转移的思路。即当我使用拷贝构造,或者赋值重载运算的时候,就将我的指针传递给另一个对象,将我架空变为nullptr。
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr)
:_ptr(ptr)
{}
auto_ptr(auto_ptr<T>& sp)
:_ptr(sp._ptr)
{
// 管理权转移
sp._ptr = nullptr;
}
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
// 检测是否为自己给自己赋值
if (this != &ap)
{
// 释放当前对象中资源
if (_ptr)
delete _ptr;
// 转移ap中资源到当前对象中
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
~auto_ptr()
{
if (_ptr)
{
delete _ptr;
}
// 像指针一样使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
}
我们不推荐使用
3.4 unique_ptr(C++11)
unique_ptr实现的一种防拷贝构造和赋值重载的思路,即在类设计时就将拷贝构造和赋值重载delete掉,其余实现与上面的同。
unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
但是他只是禁止了,还是没有解决问题
3.5 shared_ptr(C++11)
shared_ptr即共享指针,它的解决思路是引用了计数。 首先为了解决计数问题,在构造的时候就会new一块地址存储int计数器。当我们拷贝构造或者是赋值重载的时候,也要将计数器一块传给新大小,并将计算器++。同时当我们delete掉一个对象时,先--计数器,然后当计数器==0,时才会触发delete。
为了线程安全,因为++或者--的过程实际上有三步骤,重寄存器中拿出数据,++数据,存入数据,当我们有两块线程同时运行时,就会导致线程不安全问题,说以我们还要引入线程锁。在++或者--之前先上锁,之后解锁。我们在拷贝构造和赋值重载时也需要将线程锁一块传给新对象。
template <class T>
class shared_ptr {
public:
shared_ptr(T* ptr)
:_ptr(ptr),_pcont(new int(1)),_mtx(new std::mutex)
{}
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr),_pcont(sp._pcont),_mtx(sp._mtx) //拷贝构造时,将ptr,计数地址,锁一块传过去,然后addcont
{
addcont();
}
void addcont() {
_mtx->lock(); //上锁
(*_pcont)++;
_mtx->unlock();
}
int get_cont()
{
return *_pcont;
}
void release() {
_mtx->lock();
bool flag = false;
if (--(*_pcont) == 0 && _ptr) {
delete _ptr;
delete _pcont;
flag = true;
std::cout << "自动回收成功" << std::endl;
}
_mtx->unlock();
if (flag) {
delete _mtx;
}
}
~shared_ptr() { //利用RAII实现资源回收机制
release();
}
T& operator*() {
return *_ptr;
}
T* operator->() {
return _ptr;
}
T* getptr() const{
return _ptr;
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp) {
//先处理原有数据
if (this != &sp) {
if (--(*_pcont) == 0) {
release();
}
_ptr = sp._ptr;
_pcont = sp._pcont;
_mtx = sp._mtx;
addcont();
}
return *this;
}
private:
T* _ptr;
int* _pcont; //利用计数解决共享问题
std::mutex* _mtx; //解决多线程的计数不安全问题
};
但是shared_ptr也并不完善,当我们遇到循环引用问题时就会触发bug。
3.6weak_ptr(C++11专门解决循环引用问题)
当我们设计一个双链表,并用智能指针保存链表的对象中的_prev和_pnext时就出现问题。
当我本身创建时就会将计数器初始化为1,当我将我的ptr交给另一个链表对象的prev时就会++,将计数器加到2.但是当我需要删除链表的一个节点时,我只能将计数器减到1,如果想要减到0,那么我需要删除另一个节点的prev,另个节点的prev的删除需要删除另一个对象,但是另一个对象的计计数器也是2,导致了一个循环引用的问题。
为了解决这个问题,C++就提出了weak_ptr,弱指针,其实际上就是对shared_ptr的托管,将其用shared_ptr拷贝构造和赋值重载,这样就不会导致shared对象利用shared构造时的++问题。
template<class T>
class weak_ptr { //弱指针解决shared指针的循环引用问题
weak_ptr(const shared_ptr<T>& sp)
:_ptr(sp.getptr())
{}
weak_ptr<T>& operator=(const shared_ptr<T>& sp) {
_ptr = sp.getptr();
return *this;
}
T* operator->() {
return _ptr;
}
T& operator*() {
return *_ptr;
}
private:
T* _ptr;
};
4.智能指针的定制删除器
在shared_ptr的库函数实现时,其默认也是对new的单个对象进行的delete,当我们是new[]时或者是其他的malloc,fopen出的空间时,就需要在传入一个自己定制的删除器仿函数实现删除。
shared_ptr<int> array((int*)malloc(sizeof(int) * 4), [](void* x) {free(x);});
shared_ptr<FILE> a(fopen("clasiu.txt", "w"), [](FILE* f) {fclose(f); })
shared_ptr<int> array1(new int[10] , [](int* x){deletep[] x;})