前引:在传统C++开发中,手动内存管理常伴随资源泄漏、悬垂指针等隐患,成为系统稳定性的致命威胁。C++11引入的智能指针体系通过所有权语义和自动生命周期管理,从根本上重构了内存安全范式。本文将深入剖析
unique_ptr
、shared_ptr
、weak_ptr
三大核心组件的设计哲学,结合资源所有权转移模型$$ \mathcal{R}(ownership) \rightarrow \mathcal{R}_{smart}(destruction) $$揭示其如何通过编译期契约替代运行时风险,为现代C++工程注入强健性与优雅性!
目录
【一】智能指针定义与用途
(1)定义
C++11更新出来的智能指针简而言之就是帮我们解决内存忘记泄漏的问题,它本质上是一个类模板,借助类模板的Delete自动析构函数完成内存的释放。智能指针通过 RAII 机制(weak_ptr除外)管理资源,默认使用
delete
释放内存!
(2)用途
智能指针主要解决以下几个问题:
(1)忘记手动释放内存
(2)多次释放同一块内存
根据需求的不同,智能指针也有多个,下面一一介绍使用和特性,以及如何手撕!
【二】智能指针:auto_ptr
这个智能指针的设计很不实用,且给我们带来了一定的分担,总体上来说增加了工程Bug,但我们还是要了解一下!
特性:赋值的时候转移资源,很符合移动语义,但这里不是临时对象(将亡值)
例如:有一个A用auto_ptr初始化,当A拷贝给B时,A的资源就给B了,那A就变成空指针了
实现:
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr)
:_ptr(ptr)
{ }
auto_ptr(auto_ptr<T>& date)
{
_ptr = date._ptr;
date._ptr = nullptr;
}
~auto_ptr()
{
delete _ptr;
_ptr = nullptr;
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
private:
T* _ptr = nullptr;
};
【三】智能指针:unique_ptr
特性:unique单词有唯一的意思,所以这个智能指针会独占资源,即不允许转移资源
实现:
class unique_ptr
{
public:
unique_ptr(T* ptr)
:_ptr(ptr)
{ }
//不让拷贝
unique_ptr(const unique_ptr& date) = delete;
~unique_ptr()
{
delete _ptr;
_ptr = nullptr;
}
//不让赋值
void operator=(const unique_ptr& date) = delete;
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
private:
T* _ptr = nullptr;
};
【四】智能指针:share_ptr
特性:share单词有分享的意思,在这里就是同一类型共同拥有一块资源,例如:
实现:
精髓:无论是空间资源指针还是计数空间指针,都采用指向同一块资源的形式
注意:重点是下面的赋值和delete
(1)既然共同占用一块资源,那么在Delete时,应该当是最后一个对象Delete时再释放资源,否 则对同一块资源多次Delete会崩溃
(2)为了确保是最后一个资源我们需要一个计数器:这里采用的是指针,如果是普通整型,那么 当有多个同类对象时无法准确计数,采用静态变量也没有指针形式快捷简单,例如:
template<class T>
class share_ptr
{
public:
share_ptr(T* ptr)
:_ptr(ptr)
,_count(new int(1))
{ }
//拷贝构造
share_ptr(share_ptr<T>& date)
{
//共同占用资源
_ptr = date._ptr;
_count = date._count;
(*_count)++;
}
//Delete释放
~share_ptr()
{
//如果是最后一个对象说明可以释放资源了,否则减减_count即可
if (*_count == 1)
{
delete _ptr;
_ptr = nullptr;
delete _count;
_count = nullptr;
}
else
{
(*_count)--;
}
}
//赋值重载
void operator=(share_ptr<T>& date)
{
//如果是自己赋值给自己
if (_ptr == date._ptr)
{
return;
}
//开始共占资源
_ptr = date._ptr;
*_count++;
_count = date._count;
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
private:
T* _ptr = nullptr;
int* _count = nullptr;
};
效果:
可以看到同类型的share_ptr是共同占用一块资源的,而且delete是根据*_count释放的
【五】智能指针:weak_ptr
特性:weak单词有虚弱的意思,它的“虚”体现在只对资源可以访问,不参与引用计数,专门解决 share_ptr带来的循环引用问题,例如:
不支持RAII(资源的生命周期与对象的生命周期绑定),不支持单独管理资源
循环引用问题:
循环引用就是share_ptr在初始化前后互相指向的问题(类似:你抓着我的头发,我抱住你的腿,双方都说:“你先放我就放开”),逻辑:
- 初始状态:
a
指向A
对象(A
的引用计数为 1),b
指向B
对象(B
的引用计数为 1)。- 互相引用后:
a->b_ptr = b
:B
对象的引用计数从 1 变为 2(b
和a->b_ptr
共同引用)。b->a_ptr = a
:A
对象的引用计数从 1 变为 2(a
和b->a_ptr
共同引用)。- main 函数结束时:
a
离开作用域,A
对象的引用计数从 2 减为 1(剩余b->a_ptr
引用)。b
离开作用域,B
对象的引用计数从 2 减为 1(剩余a->b_ptr
引用)。- 最终状态:
A
和B
的引用计数都停留在 1(互相引用),永远不会变为 0,因此它们的析构函数不会被调用,内存永远不会释放。
实现:
只是可以访问share_ptr的资源,不参与计数
template<class T>
class weak_ptr
{
public:
//拷贝构造
weak_ptr(share_ptr<T>& date)
{
//共同占用资源
_ptr = date.Get();
}
//赋值重载
void operator=(share_ptr<T>& date)
{
//共同占用资源
_ptr = date.Get();
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
private:
T* _ptr = nullptr;
};
【六】定制删除器
(1)何为定制删除器
在 C++11 中,大部分智能指针通过 RAII 机制管理资源,默认使用
delete
释放内存。但实际开发中,资源类型远不止动态内存(如文件句柄、网络连接、数组、互斥锁等),这些资源需要特殊的释放逻辑(如fclose
、close
、delete[]
等)。定制删除器(CustomDeleter) 就是为解决这一问题而生的:它允许我们为智能指针指定自定义的资源释放逻辑,让智能指针能够管理任意类型的资源!(简而言之:处理除动态内存之外的释放资源问题)
注:删除器是一个可调用对象(函数指针、lambda、
std::function
等)
(2)为何使用定制删除器
动态数组需要定制删除器的核心原因是:
(1)动态数组通过
new[]
分配,必须用delete[]
释放(与new
/delete
不兼容)(2)智能指针默认使用
delete
释放资源,与数组的释放要求冲突(3)定制删除器可以显式指定
delete[]
,确保分配与释放逻辑匹配,避免内存泄漏或未定义行为
(3)删除器使用
传仿函数:
template<class T>
struct Function
{
void operator()(T* ptr)
{
delete[] ptr;
ptr = nullptr;
}
};
shared_ptr<string> V1(new string[10],Function<string>());
传Lambda:
shared_ptr<string> V2(new string[10], [](string* ptr) { delete[] ptr; ptr = nullptr; });
文件释放:
shared_ptr<FILE> V3(fopen("text.cpp", "c"), [](FILE* ptr) {fclose(ptr); });
(4)实现定制删除器
template<class T>
class share_ptr
{
public:
share_ptr(T* ptr)
:_ptr(ptr)
,_count(new int(1))
{ }
//定制删除器
template<class D>
share_ptr(T* ptr,D del)
:_ptr(ptr)
,_count(new int(1))
,_del(del)
{}
//拷贝构造
share_ptr(share_ptr<T>& date)
{
//共同占用资源
_ptr = date._ptr;
_count = date._count;
(*_count)++;
}
//Delete释放
~share_ptr()
{
//如果是最后一个对象说明可以释放资源了,否则减减_count即可
if (*_count == 1)
{
_del = _ptr;
delete _count;
_count = nullptr;
}
else
{
(*_count)--;
}
}
//赋值重载
void operator=(share_ptr<T>& date)
{
//如果是自己赋值给自己
if (_ptr == date._ptr)
{
return;
}
//开始共占资源
_ptr = date._ptr;
*_count++;
_count = date._count;
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
T* Get()
{
return _ptr;
}
private:
T* _ptr = nullptr;
int* _count = nullptr;
function<void(T*)> _del = [](T* ptr) {delete ptr; };
};