目录

1.定时器接口介绍

2.时间轮

3.智能指针的使用及起细节

3.1.总览

3.2.std::unique_ptr

修改器

观察器 

3.3.std::shared_ptr

修改器

观察器

别名构造函数

3.4.std::weak_ptr

修改器

观察器

辅助接口:std::enable_shared_from_this

3.5.注意事项

3.6.三大智能指针的控制块

控制块的结构与内容

控制块的创建时机

3.7.智能指针的删除器

std::unique_ptr 的删除器

std::shared_ptr 的删除器

std::weak_ptr 与删除器


1.定时器接口介绍

timerfd 是 Linux 提供的一种定时器文件描述符,它把定时器抽象成了一个可读的文件描述符
核心思想:当定时器超时时,对应的文件描述符变为“可读”,配合 epoll/select/poll 等 I/O 多路复用机制,可以将定时器事件统一到事件循环中处理,避免了传统 signal 或独立线程定时方式的复杂性

  • timerfd_create —— 创建定时器文件描述符
项目 说明
函数原型 int timerfd_create(int clockid, int flags);
头文件 <sys/timerfd.h>
功能 创建一个定时器对象,返回一个关联的文件描述符,用于后续的定时设置和事件监听
返回值 成功返回文件描述符(非负整数),失败返回 -1 并设置 errno
参数1: clockid 指定时间基准源:
• CLOCK_REALTIME:系统实时时间,受系统时间修改影响
• CLOCK_MONOTONIC:单调时间,从开机开始计时,不受系统时间修改影响
参数2: flags 设置文件描述符属性(可位或组合):
• 0:默认阻塞模式
• TFD_NONBLOCK:非阻塞模式
• TFD_CLOEXECexec 时自动关闭
使用示例 int fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
注意事项 创建后 fd 需要像普通文件一样用 close() 关闭,否则会泄露
  • timerfd_settime —— 设置/启动定时器
           struct timespec {
               time_t tv_sec;                /* Seconds */
               long   tv_nsec;               /* Nanoseconds */
           };

           struct itimerspec {
               struct timespec it_interval;  /* Interval for periodic timer */
               struct timespec it_value;     /* Initial expiration */
           };
项目 说明
函数原型 int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);
头文件 <sys/timerfd.h>
功能 启动(或修改)由 fd 指定的定时器,设置首次超时时间和周期性间隔
返回值 成功返回 0,失败返回 -1 并设置 errno
参数1: fd timerfd_create 返回的文件描述符
参数2: flags 时间解释方式:
• 0:相对时间,new_value 相对于当前时刻
• TFD_TIMER_ABSTIME:绝对时间,new_value 是绝对时间戳
参数3: new_value struct itimerspec 结构,指定新的超时设置:
• it_value:首次超时时间(设为 {0,0} 表示停止定时器)
• it_interval:后续周期性超时间隔(设为 {0,0} 表示单次触发)
参数4: old_value 若非 NULL,返回之前的定时设置,可用于恢复;不需要则传 NULL
使用示例 struct itimerspec ts = { .it_value = {2,0}, .it_interval = {1,0} };
timerfd_settime(fd, 0, &ts, NULL); // 2秒后首次,之后每秒一次
注意事项 如果 fd 已经在 epoll 中监听,调用此函数修改定时时间不会影响 epoll 注册状态
  • read —— 读取超时事件(定时器专用语义)
项目 说明
函数原型 ssize_t read(int fd, void *buf, size_t count);(标准 POSIX 接口,此处专用于 timerfd)
头文件 <unistd.h>
功能

从 timerfd 文件描述符读取超时事件

若定时器尚未超时,调用将阻塞(取决于是否设为非阻塞);超时后读取成功

返回值 成功返回读取的字节数(固定为 8,每次超时都自增),失败返回 -1 并设置 errno
参数1: fd timerfd_create 返回的文件描述符
参数2: buf 必须指向一个 uint64_t 类型的变量地址
参数3: count 必须为 sizeof(uint64_t),即 8 字节
读取内容的含义 返回的 uint64_t 数值表示:
自上次成功 read 以来,该定时器累计超时的次数
阻塞行为 • 默认(未设 TFD_NONBLOCK):无超时事件时阻塞当前线程
• 非阻塞(设 TFD_NONBLOCK):无事件时返回 -1errno 为 EAGAIN
使用示例 uint64_t exp;
ssize_t n = read(fd, &exp, sizeof(exp));
if (n == sizeof(uint64_t)) { /* 处理 exp 次超时 */ }
与 epoll 配合 当定时器超时时,fd 变为可读状态,触发 epoll 的 EPOLLIN 事件
注意事项 必须使用 uint64_t 缓冲区,且必须读取完整的 8 字节;读取后内核会清零累计计数

2.时间轮

  1. 问题起源:O(n) 遍历的痛点

在管理大量长连接时,若每次定时检查都遍历所有连接判断是否超时,绝大部分未超时的连接会被无意义地重复检查,当连接数达到十万、百万级别时,这种 O(n) 的线性扫描会严重消耗 CPU 资源,成为系统的性能瓶颈

  1. 小根堆方案

将所有连接按最近通信时间戳构建一个小根堆,每次只需检查堆顶元素是否到期即可,插入和删除的时间复杂度为 O(log n),相比全量遍历大幅提升了效率,但在超高频的定时刷新场景下,堆的调整开销仍然存在

  1. 时间轮核心思想

时间轮借鉴钟表原理,将一个环形数组划分为若干槽位,每个槽位挂载一个任务数组或链表(支持同一时刻添加多个定时任务),指针每秒前进一格,走到哪个槽就执行该槽上的所有到期任务,添加任务时根据超时时间直接 O(1) 定位到对应槽位,无需遍历也无需堆调整

  1. 单层时间轮的局限

单层时间轮的槽位数量决定了能表示的最大定时范围,如果需要支持 1 小时的定时,就需要 3600 个槽位,因此在超时范围较大时单层结构内存占用会线性膨胀

  1. 多层时间轮

多层时间轮通过"时针轮→分针轮→秒针轮"的层级结构,将大范围定时任务先存入上层轮,随时间推移逐级降级到下层轮,最终在秒针轮执行,这样只需 24 + 60 + 60 = 144 个槽位即可覆盖 24 小时的定时范围,兼顾了内存效率和 O(1) 操作复杂度

  1. 智能指针自动取消机制

将定时任务封装成一个类,在其析构函数中编写实际的超时处理逻辑(如关闭连接),每次设置或刷新超时时向时间轮投递一个 shared_ptr 指向该任务对象,由于多个 shared_ptr 共享同一对象,只有当最后一个 shared_ptr 被时间轮释放时引用计数归零,析构函数才会真正执行,从而自动实现了"旧任务被新任务覆盖即失效"的效果,无需手动删除或标记

  1. 总结

时间轮配合智能指针析构的定时任务方案,既利用时间轮的 O(1) 插入和检查特性保证了高并发下的性能,又借助 shared_ptr 的引用计数机制巧妙地解决了超时刷新时旧任务自动失效的问题

#include <iostream>
#include <cstdint>
#include <functional> 
#include <vector>
#include <memory>
#include <unordered_map>
#include <unistd.h>

//将定时任务封装成一个类
using TaskFunc = std::function<void()>;
using ReleaseFunc = std::function<void()>;

class TimerTask
{
private:
    uint64_t _id;       //定时任务编号
    uint32_t _timeout;  //超时时间
    TaskFunc _task_cb;  //定时任务函数
    ReleaseFunc _release;//销毁映射关系
    bool _canceled;     //false表示没有取消任务

public:
                                            //传引用传参:降低拷贝开销
    TimerTask(uint64_t id, uint32_t timeout, const TaskFunc& cb) :_id(id), _timeout(timeout), _task_cb(cb), _canceled(false) {}
    
    //对象析构时执行定时任务,同时销毁映射关系
    ~TimerTask()
    {
        //未被取消才执行任务
        if(!_canceled) _task_cb();
        //都要执行消除操作
        _release(); 
    } 

    void SetRelease(const ReleaseFunc& cb) { _release = cb; }
    uint32_t GetTimeout() { return _timeout; }
    void Cancel() { _canceled = true; }
};

class TimeWheel
{
private:
    using WeakPtr = std::weak_ptr<TimerTask>; //防止后续裸构造shared_ptr,导致资源被多次释放
    using TaskPtr = std::shared_ptr<TimerTask>; //管理定时任务对象
    int _tick;      //秒针
    int _capacity;  //总容量
    std::vector<std::vector<TaskPtr>> _wheel;   //二维数组的元素是定时任务对象的智能指针
    std::unordered_map<uint64_t, WeakPtr> _timers;//根据编号查找定时任务是否存在,同时使用weakptr保证任务对象可被析构
private:
    void RemoveTimer(uint64_t id)
    {
        if(_timers.count(id))
            _timers.erase(id);
    }
public:
    TimeWheel():_tick(0), _capacity(60), _wheel(_capacity){}
    ~TimeWheel(){}

    //添加任务
    void TaskAdd(uint64_t id, uint32_t timeout, const TaskFunc& cb)
    {
        //构建智能指针,添加进时间轮中
        TaskPtr pt(new TimerTask(id, timeout, cb));
        pt->SetRelease(std::bind(&TimeWheel::RemoveTimer, this, id));

        uint32_t pos = (_tick + timeout) % _capacity;
        _wheel[pos].push_back(pt);

        //建立映射关系
        _timers[id] = WeakPtr(pt);
    }

    //刷新任务
    void TaskRefresh(uint64_t id)
    {
        if(_timers.count(id))
        {
            TaskPtr pt = _timers[id].lock();//weak进行构造
            uint32_t pos = (pt->GetTimeout() + _tick) % _capacity;
            _wheel[pos].push_back(pt);
        }
    }

    //秒针移动
    void RunTimerTask()
    {
        _wheel[_tick].clear();
        _tick = (_tick + 1) % _capacity;
    }

    void Cancel(uint64_t id)
    {
        if(_timers.count(id))
        {
            TaskPtr pt = _timers[id].lock();
            pt->Cancel();
        }
    }
};

3.智能指针的使用及起细节

3.1.总览

特性 std::unique_ptr std::shared_ptr std::weak_ptr
所有权模型 独占所有权 共享所有权 非拥有(弱)引用
核心机制 禁止拷贝,仅支持移动 引用计数  观察shared_ptr管理的对象
内存与性能开销 零开销(仅封装一个裸指针) 有开销(分配控制块,维护原子操作的引用计数) 极小开销(仅存储一个指向控制块的指针)
主要用途 管理单一所有者、PImpl惯用法、工厂函数返回值 多组件共享资源、跨线程共享数据 打破循环引用、缓存观察者、观察对象生命周期

3.2.std::unique_ptr

std::unique_ptr的核心是“独占”,它不允许被拷贝,但所有权可通过std::move转移。其大小通常与裸指针相同,没有额外开销

  • 构造与析构
接口 说明
constexpr unique_ptr() noexcept; 默认构造,创建空的unique_ptr
explicit unique_ptr(pointer p) noexcept; 从裸指针构造,接管所有权
unique_ptr(unique_ptr&& u) noexcept; 移动构造,转移所有权,源指针变空
~unique_ptr(); 若非空,调用删除器释放资源

注意:拷贝构造和拷贝赋值均被删除= delete),确保独占所有权

  • 赋值操作符

接口 说明
unique_ptr& operator=(unique_ptr&& u) noexcept; 移动赋值,先释放当前资源,再接管u的资源
unique_ptr& operator=(nullptr_t) noexcept; 等价于reset(),释放当前资源
  • 修改器
接口 说明
pointer release() noexcept;

放弃所有权,返回裸指针并将自身置空

调用者需手动管理返回的指针

void reset(pointer p = pointer()) noexcept;

替换管理的对象

先用删除器销毁当前对象(如果有),然后接管p

void swap(unique_ptr& other) noexcept; 交换两个unique_ptr管理的指针和删除器
  • 观察器 

接口 说明
pointer get() const noexcept; 返回存储的裸指针,不放弃所有权
explicit operator bool() const noexcept; 检查是否非空(是否有管理对象)
T& operator*() const; 解引用,获得对象引用(要求非空,否则行为未定义)
pointer operator->() const noexcept; 返回裸指针,用于成员访问
T& operator[](size_t i) const; 仅对数组特化有效,访问数组第i个元素
  • 数组特化 (std::unique_ptr<T[]>)

当模板参数为T[]时,提供operator[]代替operator*operator->,且删除器默认调用delete[]

std::unique_ptr<int[]> arr = std::make_unique<int[]>(5);
arr[0] = 10; // 使用 operator[]
  • 自定义删除器

删除器作为第二个模板参数,类型需为可调用对象

// 函数指针类型
std::unique_ptr<FILE, decltype(&fclose)> fp(fopen("a.txt", "r"), &fclose);
  • 非成员函数
接口 说明
std::make_unique<T>(Args&&... args)

(C++14) 创建unique_ptr<T>,转发参数构造对象

异常安全,推荐使用

std::make_unique<T[]>(size_t size) (C++14) 创建管理数组的unique_ptr<T[]>
bool operator==(const unique_ptr& x, nullptr_t) 等 nullptr比较
void swap(unique_ptr& a, unique_ptr& b) noexcept; 特化的std::swap

3.3.std::shared_ptr

std::shared_ptr允许多个智能指针共享同一对象,通过“引用计数”来管理生命周期

  • 构造与析构
接口 说明
constexpr shared_ptr() noexcept; 默认构造,空指针
explicit shared_ptr(Y* p); 从裸指针构造,创建控制块,引用计数初始为1
shared_ptr(const shared_ptr& r) noexcept; 拷贝构造,引用计数+1
shared_ptr(shared_ptr&& r) noexcept; 移动构造,转移所有权,r变空,引用计数不变
template<class Y> shared_ptr(const shared_ptr<Y>& r) noexcept; 从派生类指针构造(协变)
template<class Y> shared_ptr(const weak_ptr<Y>& r); weak_ptr提升构造,若weak_ptr已过期则抛出std::bad_weak_ptr
template<class Y, class D> shared_ptr(Y* p, D d); 使用自定义删除器d
shared_ptr(std::nullptr_t) noexcept; 构造空shared_ptr
~shared_ptr(); 引用计数-1,若变为0则释放资源

只有从已有 shared_ptr / weak_ptr 间接构造才共享计数;
直接包裸指针、新建、移动均为独立控制块

构造方式 是否共享控制块 & 引用计数 说明
拷贝已有的 shared_ptr 共享 计数 + 1,同一块资源
weak_ptr::lock() 提升 共享 安全观测,同一块资源
别名构造(alias 构造) 共享 共享生命周期,指向不同子对象
裸指针直接构造  独立 新建控制块,易造成重复释放
单独 make_shared  独立 全新对象 + 全新控制块
移动构造(std::move 不增不减 所有权转移,计数不变
unique_ptr 移动构造  新建 转移所有权并新建控制块
  • 赋值操作符

接口 说明
shared_ptr& operator=(const shared_ptr& r) noexcept; 拷贝赋值,先递减当前计数,再递增r的计数
shared_ptr& operator=(shared_ptr&& r) noexcept; 移动赋值,所有权转移
shared_ptr& operator=(std::nullptr_t) noexcept; 释放当前资源(reset()
  • 修改器
接口 说明
void reset() noexcept; 释放所有权,引用计数-1
template<class Y> void reset(Y* p); 接管新指针p,释放旧资源
template<class Y, class D> void reset(Y* p, D d); 带删除器接管新指针
void swap(shared_ptr& other) noexcept; 交换两个shared_ptr(包括指针和控制块)
  • 观察器
接口 说明
element_type* get() const noexcept; 返回裸指针
long use_count() const noexcept; 返回强引用计数(即当前shared_ptr的副本数)
explicit operator bool() const noexcept; 是否非空
T& operator*() const noexcept; 解引用(要求非空)
T* operator->() const noexcept; 成员访问
  • 别名构造函数

template<class Y> shared_ptr(const shared_ptr<Y>& r, element_type* p) noexcept;

功能:创建一个与r共享控制块(即生命周期与r相同),但指向不同对象pshared_ptr

常用于指向某个对象的成员变量,并保证该对象在成员被访问时仍存活

struct Bar { int data; };
auto foo = std::make_shared<Bar>();
std::shared_ptr<int> alias(foo, &foo->data); // 生命周期与foo绑定
  • 非成员函数

接口 说明
std::make_shared<T>(Args&&... args) 创建shared_ptr<T>,一次分配对象和控制块,高效且异常安全
std::allocate_shared<T>(Alloc alloc, Args&&... args) 使用自定义分配器创建
bool operator== 等比较运算符 支持与nullptr、其他shared_ptr比较
template<class T, class U> shared_ptr<T> static_pointer_cast(const shared_ptr<U>& r); 静态转换智能指针类型
template<class T, class U> shared_ptr<T> dynamic_pointer_cast(const shared_ptr<U>& r); 动态转换(运行时类型检查)
template<class T, class U> shared_ptr<T> const_pointer_cast(const shared_ptr<U>& r); 常量性转换
template<class T, class U> shared_ptr<T> reinterpret_pointer_cast(const shared_ptr<U>& r); 重新解释转换(C++17)

3.4.std::weak_ptr

std::weak_ptr是shared_ptr的“观察者”,它不增加引用计数,专门用来打破shared_ptr间的循环引用

  • 构造与赋值

接口 说明
constexpr weak_ptr() noexcept; 默认构造,空指针
weak_ptr(const weak_ptr& r) noexcept; 拷贝构造,弱引用计数+1
weak_ptr(const shared_ptr<Y>& r) noexcept; shared_ptr构造,弱引用计数+1,强引用计数不变
weak_ptr(weak_ptr&& r) noexcept; 移动构造
~weak_ptr(); 弱引用计数-1,若控制块的强、弱计数均为0,则销毁控制块
  • 赋值操作符

赋值操作符 行为说明
weak_ptr& operator=(const weak_ptr& r) noexcept; 拷贝赋值
先减少当前指针指向控制块的弱引用计数
再增加 r 指向控制块的弱引用计数
更新内部指针指向 r 所观察的对象
weak_ptr& operator=(weak_ptr&& r) noexcept; 移动赋值
先减少当前控制块的弱引用计数
将 r 的内部状态直接转移给 *this弱引用计数不变
操作后 r 变为空(expired() 为 true
weak_ptr& operator=(const shared_ptr<Y>& r) noexcept; 从 shared_ptr 赋值
先减少当前控制块的弱引用计数
增加 r 指向控制块的弱引用计数
开始观察 r 管理的对象,强引用计数不变
weak_ptr& operator=(std::nullptr_t)  置空赋值
效果同 reset(),释放当前观察,弱引用计数减一

注:当控制块的强引用计数和弱引用计数均归零时,控制块所占内存会被自动释放(由 shared_ptr 或最后一个 weak_ptr 的析构触发)

  • 修改器
接口 说明
void reset() noexcept; 清空weak_ptr,弱引用计数-1
void swap(weak_ptr& r) noexcept; 交换
  • 观察器
接口 说明
long use_count() const noexcept; 返回强引用计数(管理对象的shared_ptr数量)
bool expired() const noexcept; 检查对象是否已销毁(即use_count() == 0
std::shared_ptr<T> lock() const noexcept; 最关键的方法。尝试提升为shared_ptr。若expired()true,返回空shared_ptr;否则返回一个共享所有权的shared_ptr
  • 非成员函数

std::weak_ptr支持与shared_ptrnullptr的比较运算符(==!=),以及std::swap特化。

  • 辅助接口:std::enable_shared_from_this<T>

当一个类需要安全地获取自身的shared_ptr时(例如在成员函数中返回一个指向自己的shared_ptr),应公有继承此类

成员函数 说明
shared_ptr<T> shared_from_this(); 返回一个与当前对象共享所有权的shared_ptr<T>前提:该对象必须已被某个shared_ptr管理,否则行为未定义(C++17前抛出std::bad_weak_ptr
weak_ptr<T> weak_from_this() noexcept; (C++17) 返回一个weak_ptr,效果同shared_from_this()但无需立即提升
  • enable_shared_from_this 通过在对象内部存储一个指向控制块的弱指针来工作
  • 当调用 shared_from_this() 时,从该弱指针提升(lock),返回一个新的 shared_ptr
  • 由于所有提升操作都指向同一个控制块,因此只会增加引用计数,不会创建新的控制块
  • 这保证了对象的所有权管理是统一且正确

shared_from_this() 的工作原理是:通过一个内部弱引用 weak_ptr 来生成 shared_ptr

但这个弱引用必须等到至少有一个 shared_ptr 管理该对象时才会被初始化

使用示例

class Good : public std::enable_shared_from_this<Good> {
public:
    std::shared_ptr<Good> getShared() {
        return shared_from_this(); // 安全
    }
    //  错误做法: return std::shared_ptr<Good>(this);
    //用同一个 this 构造了独立的智能指针,产生多个控制块,导致双重释放
};

3.5.注意事项

  • unique_ptr 没有 use_count(),因为它独占,计数恒为1或0
  • weak_ptr::lock() 是原子操作,开销与shared_ptr拷贝相当,但它是安全访问对象的唯一途径
  • 避免从this直接构造shared_ptr,除非使用了enable_shared_from_this

3.6.三大智能指针的控制块

控制块是 shared_ptr 和 weak_ptr 实现共享所有权与弱观察的核心数据结构,它独立于被管理对象,存储生命周期管理所需信息(指向相同的两种指针共用同一个控制块)

unique_ptr 没有控制块,因为它独占且无引用计数

  • 控制块的结构与内容

一个典型的控制块包含以下字段(具体实现可不同):

字段 说明
强引用计数 当前指向该对象的 shared_ptr 实例数量。归零时销毁被管理对象。
弱引用计数  当前指向该对象的 weak_ptr 实例数量。注意:弱引用计数通常包含强引用计数的一个隐含份额(如强弱计数总和 = shared_count + weak_count + 1 标记位),以确保控制块自身在最后一个 shared_ptr 析构后仍能为 weak_ptr 提供 expired() 查询,直到所有 weak_ptr 也析构。
删除器  可调用对象,用于销毁被管理对象(类型擦除存储)。
分配器 用于分配/释放控制块内存(若使用 allocate_shared)。
被管理对象的指针 可能不同于 shared_ptr::get() 返回的指针(例如存在虚继承或别名构造时)。
  • 控制块的创建时机

创建方式 控制块分配次数 特点
std::make_shared<T>(...) 一次分配 将对象内存和控制块内存在一块连续内存中分配,效率高、缓存友好。但对象内存与控制块绑定,即使强引用计数归零、弱引用计数仍存在时,对象所占内存也无法释放(对象已析构,但内存块保留)。
std::shared_ptr<T>(new T(...)) 两次分配 先 new 分配对象,再分配控制块。效率稍低,但对象内存可在强引用计数归零时立即释放,不受弱引用计数影响。
从 weak_ptr 拷贝构造/赋值 shared_ptr 不创建新控制块 复用已有控制块,增加强引用计数。
别名构造函数 (shared_ptr(const shared_ptr<Y>&, T*)) 不创建新控制块 复用源 shared_ptr 的控制块,仅改变内部存储的指针。
  • 强引用计数归零时:调用删除器销毁被管理对象(执行 T::~T() 或自定义删除器)
  • 弱引用计数也归零时:释放控制块自身占用的内存(调用 delete 或自定义分配器的释放操作)
  • 若使用 make_shared,对象内存与控制块一同分配,因此即使强引用计数归零、对象已析构,只要还存在任何 weak_ptr,整个内存块就不会归还操作系统(但对象本身已不可访问)。对于大对象或内存敏感场景,可考虑使用 new 方式以允许对象内存在强计数归零时立即释放。

3.7.智能指针的删除器

删除器是一个可调用对象,用于在资源需要释放时执行自定义清理逻辑

默认删除器为 std::default_delete<T>,其行为是 delete ptr 或 delete[] ptr

智能指针 删除器存储位置 类型擦除 对指针类型的影响
std::unique_ptr 智能指针对象内部(作为数据成员) 删除器类型不同 → 不同的 unique_ptr 类型
std::shared_ptr 控制块内部 删除器不同但 shared_ptr<T> 类型相同
std::weak_ptr 不适用(无所有权) - -
  1. std::unique_ptr 的删除器

template<typename T, typename Deleter = std::default_delete<T>>
class unique_ptr;

删除器作为 unique_ptr 的第二个模板参数,是类型的一部分

特性 说明
默认行为 delete 单个对象(对 T[] 特化调用 delete[]
自定义删除器语法 必须显式指定删除器类型,并在构造时传入可调用对象实例
类型擦除 。删除器类型是编译期固定的,不同删除器的 unique_ptr 是不同类型,不能相互赋值
常见用法 管理 FILE*socket、内存池分配的内存等

自定义删除器示例:

// 1. 函数指针类型
void close_file(FILE* fp) { if (fp) fclose(fp); }
std::unique_ptr<FILE, decltype(&close_file)> fp1(fopen("a.txt", "r"), close_file);

// 2. 使用 Lambda(C++11 后推荐)
auto file_deleter = [](FILE* fp) { if (fp) fclose(fp); };
std::unique_ptr<FILE, decltype(file_deleter)> fp2(fopen("b.txt", "r"), file_deleter);

// 3. 函数对象
struct FileCloser { void operator()(FILE* fp) const { fclose(fp); } };
std::unique_ptr<FILE, FileCloser> fp3(fopen("c.txt", "r"));
写法 删除器类型 构造时是否传参 为什么?
函数指针 decltype(&close_file) 必须传 类型是 void (*)(FILE*),默认构造值为 nullptr
Lambda decltype(file_deleter) 必须传 Lambda 闭包类型默认构造被 =delete
函数对象 FileCloser 不需要传 类型自身是可默认构造的空类
  1. std::shared_ptr 的删除器

删除器不作为模板参数,而是存储在控制块中进行类型擦除

特性 说明
默认行为

delete 单个对象(std::default_delete<T>

注意:shared_ptr 不支持 T[] 的默认 delete[] 行为

(C++17 后引入 shared_ptr<T[]> 支持)

自定义删除器语法 构造函数参数中直接传入可调用对象,无需指定模板类型
类型擦除 不同删除器的 shared_ptr 具有相同类型 shared_ptr<T>,可以互相赋值和存储在同一容器中
存储位置 删除器作为控制块的一部分(通常使用虚函数或函数指针实现类型擦除)

自定义删除器示例:

// 1. Lambda(最常用)
auto deleter = [](FILE* fp) { if (fp) fclose(fp); };
std::shared_ptr<FILE> sp1(fopen("d.txt", "r"), deleter);

// 2. 函数指针
std::shared_ptr<FILE> sp2(fopen("e.txt", "r"), &fclose);

// 3. 管理动态数组(C++17 之前)
std::shared_ptr<int> sp3(new int[10], [](int* p) { delete[] p; });

// C++17 后支持数组特化
std::shared_ptr<int[]> sp4(new int[10]); // 默认调用 delete[]
  1. std::weak_ptr 与删除器

weak_ptr 不拥有对象,因此不涉及删除器

当它通过 lock() 提升为 shared_ptr 时,提升后的 shared_ptr 会沿用控制块中存储的删除器

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐