0. 内存泄漏
内存泄露 :因为错误或疏忽,造成程序未能释放某段不再使用的内存的情景。
内存泄漏的危害 :长期运行的程序出现内存泄露,如:操作系统等,会导致响应越来越慢,最终卡死。
在正式了解之前,需要明晰一个观念,否则容易把 智能指针的作用 和 自定义类型生命周期结束时会自动调用析构函数 混淆:
智能指针的出现,是为了解决传统指针内存泄露的问题。
智能指针的使用及原理
1. RAII
RAII(Resource Acquisition Is Initialization):资源获取即初始化 ,利用对象生命周期来控制资源。
- 在构造对象时自动获取资源
- 在对象析构时自动释放资源
实际上,我们把管理一份资源的责任委托给一个对象,这样做有两个好处:
- 不需要显式释放资源
cpp
// 传统指针
int* pa = new int[10];
...
delete[] pa;
// 智能指针(包括接下来会介绍到的 unique_ptr、shared_ptr)
unque_ptr<int[]> pb(new int[10]);
// 智能指针对象析构时,会自动释放资源,不需要显式 delete
- 对象所需的资源在其生命周期内始终有效
cpp
class MyClass {
public:
MyClass(int data) : _data(data) {
std::cout << "MyClass constructed with data: " << _data << std::endl;
}
~MyClass() {
std::cout << "MyClass destructed." << std::endl;
}
void PrintData() const {
std::cout << "Data: " << _data << std::endl;
}
private:
int _data;
};
int main() {
// 使用 std::unique_ptr 创建一个 MyClass 对象
std::unique_ptr<MyClass> my_instance = std::make_unique<MyClass>(42);
// 可以像普通指针一样使用 my_instance
my_instance->PrintData();
// 当 my_instance 超出作用域时,其析构函数会自动调用,释放 MyClass 实例
// 无需手动 delete
return 0;
}
2. 智能指针的原理
- RAII
cpptemplate<class T> class SmartPtr { public: SmartPtr(T* ptr) :_ptr(ptr) {} ~SmartPtr() { if (_ptr) delete _ptr; } private: T* _ptr; };
- 重载 operator*() operator->()
cpppublic: T& operator*() { return *_ptr; } T* operator->() { return _ptr; }
3. unique_ptr
cpp
template<class T>
class unique_ptr
{
public:
unique_ptr(T* ptr)
:_ptr(ptr)
{}
~unique_ptr()
{
if (_ptr)
{
cout << "~unique_ptr()" << endl;
delete _ptr;
_ptr = nullptr;
}
}
// 禁止拷贝
unique_ptr(unique_ptr<T>&) = delete;
unique_ptr<T>& operator=(unique_ptr<T>&) = delete;
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
4. shared_ptr shared_ptr
在 unique_ptr 基础上加入了引用计数,解决拷贝导致资源管理权转移的问题 ------ 一份资源对应一个引用计数;
引用计数 代表着 对应的资源被几个 shared_ptr 所共有;
在对象被销毁(析构)时,--(*_psize)
;
*_psize == 0
时,delete _ptr
释放资源。
cpp
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr)
:_ptr(ptr)
,_psize(new int(1))
{}
void release()
{
if (--(*_psize) == 0)
{
if (_ptr)
{
cout << "delete: " << _ptr << endl;
delete _ptr;
_ptr = nullptr;
}
delete _psize; // _psize 是在堆上开空间,需要 delete
}
}
~shared_ptr()
{
release();
}
shared_ptr(shared_ptr<T>& sp)
:_ptr(sp._ptr)
,_psize(sp._psize)
{
++(*_psize);
}
int use_count()
{
return *_psize;
}
shared_ptr<T>& operator=(shared_ptr<T>& sp)
{
if (_ptr != sp._ptr)
{
release();
_ptr = sp._ptr;
_psize = sp._psize;
++(*_psize);
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get()
{
return _ptr;
}
private:
T* _ptr;
// 引用计数支持多个拷贝管理同一个资源,最后一个析构对象释放资源
int* _psize;
};
shared_ptr 同样有设计缺陷:循环引用。
if (--(*_psize) == 0)
才真正释放资源,循环引用
会导致资源无法被释放:
左边节点的释放,依赖于 delete 右边节点的 _prev;
delete 右边节点的 _prev,依赖于 右边节点的释放;
右边节点的释放,依赖于 delete 左边节点的 _next;
delete 左边节点的 _next,依赖于 左边节点的释放 ... ... (如此循环下去,两个节点的资源都无法被释放)
因此,需要引入 weak_ptr 。
weak_ptr
不会增加它所指向对象的引用计数,它不会影响 shared_ptr
所管理对象的生命周期,可以很好解决循环引用的问题。
cpp
template<class T>
class weak_ptr
{
public:
weak_ptr(T* ptr)
:_ptr(ptr)
{}
weak_ptr(shared_ptr<T>& sp)
{
_ptr = sp.get();
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};