当前位置 博文首页 > m1059247324的博客:C++ 内存泄漏和智能指针介绍
什么是内存泄漏 ?
内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
泄漏的产生
内存泄漏的分类:
内存泄漏的危害
内存泄露最明显最直接的影响就是导致系统中可用的内存越来越少,直到所有的可用内存用完最后导致系统无可用内存而崩溃。
如果导致泄露的操作是一次性的,或是不经常的,一般问题都不大。在应用退出或系统退出时会清理内存(进程的正常退出都是会自动释放内存的);
如果导致泄露的操作是经常性的或是循环的,则内存会最终消耗完(或很短时间内)而导致系统崩溃。
为什么要使用智能指针
因为 C++ 不像 Java 一样具有垃圾回收机制(GC),但是又需要释放内存,防止内存泄漏。智能指针的作用是管理一个指针,主要用于防止内存写了,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。
1.auto_ptr
auto_ptr 的大概流程就是使用该指针去代替原生指针管理空间,而后将原生指针置空,这样在 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;
}
// ap1 = ap2
auto_ptr<T>& operator=(const auto_ptr<T>& ap)
{
if (this != &ap)
{
if (_ptr)
delete _ptr;
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
~auto_ptr()
{
if (_ptr)
{
delete _ptr;
_ptr = nullptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
2.unique_ptr
unique_pt r实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。同时它还设置了防拷贝,避免出现一个空间释放多次的现象,它对于避免资源泄露(new 后忘记 delete )特别有用。它在创建时自动加锁,在销毁时自动解锁
缺点: 因为设置了防拷贝,所在在涉及到拷贝的场景下,它无法使用。
//unique_ptr 的简单模拟
template<class T>
class unique_ptr
{
public:
unique_ptr(T* ptr)
:_ptr(ptr)
{}
unique_ptr(unique_ptr<T>& up) = delete; //防止拷贝构造
unique_ptr<T>& operator=(unique_ptr<T>& up) = delete; //防止赋值
~unique_ptr()
{
if (_ptr)
{
delete _ptr;
_ptr = nullptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
3.shared_ptr
是线程安全的共享式智能指针,同一块空间由多个指针一同进行管理,它使用计数器来查看该空间被几个指针所共享,每当一个指针指向该空间,计数器 + 1,每当一个指针退出后,计数器 -1,只有在计数器 =0 时才会彻底释放该空间资源。
//shared_ptr 的简单模拟
template<class T>
class shared_ptr
{
public:
shared_ptr(T *ptr = nullptr)
:_ptr(ptr),_pcount(new int(1)),_mtx(new mutex){}
shared_ptr(const shared_ptr<T>& sp)
: _ptr(sp._ptr), _pcount(sp._pcount),_mtx(sp._mtx)
{
plus();
}
//sp1 = sp4,就是让sp1释放原理管理的空间(如果其 pcount == 1),再去和sp4共同管理sp4的空间
shared_ptr<T>& operator= (shared_ptr<T>& sp)
{
if (this != &sp)
{
release();
_ptr = sp._ptr;
_pcount = sp._pcount;
_mtx = sp._mtx;
plus();
}
return *this;
}
void plus() //上锁计数器++
{
_mtx->lock();
++(*_pcount);
_mtx->unlock();
}
void release()
{
bool flag = false;
_mtx->lock();
if (--(*_pcount) == 0)
{
if (_ptr)
{
delete _ptr;
_ptr = nullptr;
}
delete _pcount;
_pcount = nullptr;
flag = true;
}
_mtx->unlock();
if (flag == true) //连互斥锁也释放
{
delete _mtx;
_mtx = nullptr;
}
}
T* operator->()
{
return _ptr;
}
T& operator* ()
{
return *_ptr;
}
int use_count()
{
return *_pcount;
}
~shared_ptr()
{
release();
}
private:
T* _ptr;
int* _pcount;
mutex* _mtx; //定义成指针,因为计数器是指针类型的
};
缺点:在循环引用的情况下会出错
循环引用
4. week_ptr
weak_ptr 是一种不控制对象生命周期的智能指针, 严格来说其实不算智能指针(因为它没有 RAII 管理机制),它是为了解决 shared_ptr 在遇到循环引用时的问题而诞生的 (将链表中的节点改为 week_ptr 即可)。它的构造和析构不会引起引用记数的增加或减少。
template<class T>
class weak_ptr
{
public:
weak_ptr() = delete;
weak_ptr(const shared_ptr<T>& sp) //不能拷贝原生指针,智能拷贝 shared_ptr 指针
:_ptr(sp.get_ptr())
{}
weak_ptr<T>& operator = (const shared_ptr<T>& sp)
{
_ptr = sp.get_ptr();
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
lock_guard
非智能指针,而是一种 RAII 管理机制,当程序中有共享数据时,你不想让程序其陷入条件竞争,或是出现不变量被破坏的情况,此时可使用std::mutex互斥量来解决数据共享的问题。C++标准库为互斥量提供了一个RAII语法的模板类std::lock_guard,在构造时就能提供已锁的互斥量,并在析构的时候进行解锁,从而保证了一个已锁互斥量能被正确解锁。
lock_guard 和 unique_ptr 的区别
lock_guard和unique_lock都是RAII机制下的锁,即依靠对象的创建和销毁也就是其生命周期来自动实现一些逻辑,而这两个对象都是在创建时自动加锁,在销毁时自动解锁。所以如果仅仅是依靠对象生命周期实现加解锁的话,两者是相同的,都可以用,因跟生命周期有关,所以有时会用花括号指定其生命周期。但lock_guard的功能仅限于此。unique_lock是对lock_guard的扩展,允许在生命周期内再调用lock和unlock来加解锁以切换锁的状态。
cs