讲师中心 微信公众号

工具推荐

视频效率加速
立刻MV
立刻MV

低成本歌曲 MV、视频生成

updream

一键生成高质量推广成片

Laper
Laper

剧本策划到成片协作

剧云
剧云

中文短剧剧本制作平台

C++智能指针自定义删除器 资源清理回调

云涛君_8680

云涛君_8680

发布时间:2025-09-01 08:47:01

|

960人浏览过

|

来源于php中文网

原创

自定义删除器使智能指针能管理文件句柄、网络连接等非内存资源,通过RAII确保资源安全释放,提升代码健壮性与通用性。

c++智能指针自定义删除器 资源清理回调

C++智能指针的自定义删除器,本质上就是为智能指针提供一个“如何释放”的特殊指令,让它在管理内存之外,还能妥善处理文件句柄、网络连接或其他需要特定清理流程的资源。这使得智能指针的应用范围大大扩展,从单纯的内存管理工具升级为通用的资源管理利器,让资源管理变得更加自动化、安全且富有弹性。

解决方案

在C++中,智能指针(主要是

std::unique_ptr
和
std::shared_ptr
)默认知道如何使用
delete
操作符来释放它们所管理的堆内存。但现实世界中,我们常常需要管理那些并非通过
new
分配,或者需要特定函数(如
fclose
、
CloseHandle
、
free
等)来释放的资源。这时候,自定义删除器就派上用场了。它允许我们告诉智能指针,当它不再需要管理某个资源时,应该调用哪个函数或执行哪段代码来完成清理工作。

我们通常有几种方式来定义这个“清理指令”:函数对象(Functor)、Lambda表达式或者普通的函数指针。

1. 使用

std::unique_ptr
自定义删除器

立即学习“C++免费学习笔记(深入)”;

std::unique_ptr
的自定义删除器类型是其模板参数的一部分,这意味着如果你使用了一个带有自定义删除器的
unique_ptr
,它的完整类型会包含这个删除器类型。

  • 函数对象(Functor)作为删除器: 当清理逻辑比较复杂,或者希望删除器能携带一些状态时,函数对象是个不错的选择。

    #include <iostream>
    #include <memory>
    #include <cstdio> // For FILE*
    
    // 定义一个文件关闭器,它是一个函数对象
    struct FileCloser {
        void operator()(FILE* fp) const {
            if (fp) {
                std::cout << "DEBUG: Closing file handle via FileCloser." << std::endl;
                fclose(fp);
            }
        }
    };
    
    void demo_unique_ptr_functor() {
        // unique_ptr的第二个模板参数需要指定删除器的类型
        std::unique_ptr<FILE, FileCloser> logFile(fopen("app.log", "w"));
        if (logFile) {
            fprintf(logFile.get(), "Application started.\n");
            // ... 更多操作 ...
        }
        std::cout << "INFO: logFile unique_ptr scope ending." << std::endl;
        // 当logFile超出作用域时,FileCloser::operator()会被调用
    }
  • Lambda表达式作为删除器: 对于一次性、局部性的清理任务,Lambda表达式非常简洁高效,尤其当它需要捕获一些上下文变量时。

    #include <iostream>
    #include <memory>
    #include <thread> // For std::jthread example
    #include <utility> // For std::move
    
    // 假设我们有一个需要join的线程
    void demo_unique_ptr_lambda_thread() {
        auto thread_deleter = [](std::jthread* t) {
            if (t && t->joinable()) {
                std::cout << "DEBUG: Joining thread via lambda deleter." << std::endl;
                t->join();
                delete t; // 别忘了释放线程对象本身
            } else if (t) {
                delete t;
            }
        };
    
        // unique_ptr的第二个模板参数需要通过decltype获取lambda的类型
        std::unique_ptr<std::jthread, decltype(thread_deleter)>
            worker(new std::jthread([]{
                std::cout << "Worker thread running..." << std::endl;
                std::this_thread::sleep_for(std::chrono::milliseconds(100));
                std::cout << "Worker thread finished." << std::endl;
            }), thread_deleter);
    
        std::cout << "INFO: Main thread waiting for worker." << std::endl;
        // worker超出作用域时,lambda会被调用,确保线程被join
    }
  • 函数指针作为删除器: 最简单直接的方式,适用于无状态的全局或静态清理函数。

    #include <iostream>
    #include <memory>
    #include <cstdlib> // For free
    
    // 一个通用的free函数,适用于通过malloc分配的内存
    void free_deleter(void* ptr) {
        if (ptr) {
            std::cout << "DEBUG: Calling free() via function pointer deleter." << std::endl;
            free(ptr);
        }
    }
    
    void demo_unique_ptr_func_ptr() {
        // unique_ptr的第二个模板参数是函数指针类型
        std::unique_ptr<int, decltype(&free_deleter)> data(
            static_cast<int*>(malloc(sizeof(int))), &free_deleter);
        if (data) {
            *data = 42;
            std::cout << "Data allocated with malloc: " << *data << std::endl;
        }
        std::cout << "INFO: data unique_ptr scope ending." << std::endl;
        // data超出作用域时,free_deleter会被调用
    }

2. 使用

std::shared_ptr
自定义删除器

std::shared_ptr
处理自定义删除器的方式与
unique_ptr
略有不同。它的删除器类型不是其自身模板参数的一部分,而是作为构造函数的一个参数传递。这意味着所有
shared_ptr<T>
实例都可以有不同的删除器,而它们的类型仍然是
std::shared_ptr<T>
。这得益于
shared_ptr
内部的类型擦除机制。

C++14
C++14

C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。

下载
#include <iostream>
#include <memory>
#include <string>
#include <fstream> // For std::ofstream

// 假设我们有一个自定义的资源类,需要特定的关闭方法
class NetworkConnection {
    std::string _host;
    int _port;
    bool _isOpen = false;
public:
    NetworkConnection(const std::string& host, int port) : _host(host), _port(port) {
        std::cout << "NetworkConnection to " << _host << ":" << _port << " established." << std::endl;
        _isOpen = true;
    }
    void send(const std::string& data) {
        if (_isOpen) {
            std::cout << "Sending data to " << _host << ": " << data << std::endl;
        }
    }
    void close() {
        if (_isOpen) {
            std::cout << "NetworkConnection to " << _host << ":" << _port << " closed." << std::endl;
            _isOpen = false;
        }
    }
    // 析构函数,但我们希望通过自定义删除器来调用close()
    ~NetworkConnection() {
        std::cout << "NetworkConnection destructor called (should be after custom close)." << std::endl;
    }
};

void demo_shared_ptr_custom_resource() {
    // shared_ptr在构造时直接传入删除器,类型是std::shared_ptr<NetworkConnection>
    std::shared_ptr<NetworkConnection> conn(
        new NetworkConnection("example.com", 8080),
        [](NetworkConnection* nc) {
            if (nc) {
                std::cout << "DEBUG: Custom deleter for NetworkConnection called." << std::endl;
                nc->close(); // 调用资源的特定关闭方法
                delete nc;   // 然后释放内存
            }
        }
    );

    if (conn) {
        conn->send("Hello, server!");
    }
    std::cout << "INFO: conn shared_ptr scope ending." << std::endl;
    // 当conn的引用计数归零时,lambda删除器会被调用
}

// 另一个shared_ptr例子:管理std::ofstream
void demo_shared_ptr_ofstream() {
    std::shared_ptr<std::ofstream> outFile(
        new std::ofstream("output.txt"),
        [](std::ofstream* os) {
            if (os && os->is_open()) {
                std::cout << "DEBUG: Custom deleter closing std::ofstream." << std::endl;
                os->close();
                delete os;
            } else if (os) {
                delete os;
            }
        }
    );

    if (outFile && outFile->is_open()) {
        *outFile << "Writing something to output.txt\n";
    }
    std::cout << "INFO: outFile shared_ptr scope ending." << std::endl;
}

int main() {
    demo_unique_ptr_functor();
    std::cout << "------------------------\n";
    demo_unique_ptr_lambda_thread();
    std::cout << "------------------------\n";
    demo_unique_ptr_func_ptr();
    std::cout << "------------------------\n";
    demo_shared_ptr_custom_resource();
    std::cout << "------------------------\n";
    demo_shared_ptr_ofstream();
    return 0;
}

可以看到,自定义删除器极大地扩展了智能指针的能力,让它们能够成为管理各种类型资源的通用工具,而不仅仅是内存。

C++智能指针自定义删除器的核心价值:超越内存管理的资源封装

说实话,刚接触智能指针时,很多人(包括我)可能只把它当成

new
/
delete
的自动版本。但随着项目深入,你会发现很多资源并非简单地用
delete
就能搞定。比如文件句柄需要
fclose
,网络套接字需要
closesocket
,互斥锁需要
unlock
,甚至一些C库分配的内存需要
free
而不是
delete
。这些非内存资源的清理逻辑,如果每次都靠手动调用,那简直是噩梦。一个不小心,资源泄漏就发生了,调试起来非常痛苦。

自定义删除器就是为了解决这些痛点而生的。它把资源获取(Resource Acquisition)和资源释放(Resource Release)紧密地绑定在一起,完美地体现了RAII(Resource Acquisition Is Initialization)原则。当我拿到一个文件句柄,我立刻把它包装进一个带有

fclose
删除器的
unique_ptr
里,那么无论我的代码在中间出了什么岔子,抛了异常也好,提前返回也罢,只要这个
unique_ptr
离开作用域,文件句柄就一定会被妥善关闭。这不仅大大提升了代码的健壮性和异常安全性,也让资源管理逻辑变得更加清晰和集中。从某种意义上说,它将智能指针从一个“内存管家”升级成了“全能资源管理员”。

C++智能指针自定义删除器选择指南:函数对象、Lambda与函数指针的优劣

在选择自定义删除器的实现方式时,我通常会根据实际需求来权衡。这三种方式各有千秋,没有绝对的“最佳”,只有“最适合”。

  • 函数对象(Functor): 这是最灵活的一种方式,因为它本质上是一个类。你可以让它携带状态,比如在删除器中记录一些日志信息,或者根据内部状态决定不同的清理策略。如果你的清理逻辑比较复杂,或者希望这个删除器可以在多个地方复用,并且需要一些配置参数,那么函数对象是首选。它的缺点是需要额外定义一个

    struct
    或
    class
    ,对于简单的清理任务来说,可能会显得有点“杀鸡用牛刀”。另外,对于
    std::unique_ptr
    ,函数对象的类型会成为
    unique_ptr
    类型的一部分,这在模板元编程或类型推导时可能需要额外注意。

  • Lambda 表达式: 这是我个人最常用的一种方式,尤其是在C++11及更高版本中。Lambda表达式的优势在于简洁和局部性。你可以在需要的地方直接定义清理逻辑,并且可以轻松捕获当前作用域的变量,这使得它非常适合处理那些与特定上下文相关的资源清理。例如,一个删除器可能需要访问某个日志对象来记录关闭信息,或者需要一个ID来标识正在关闭的资源。Lambda的缺点嘛,对于

    std::unique_ptr
    ,你可能需要使用
    decltype
    来获取lambda的类型,这让代码看起来略微复杂一点点。但对于
    std::shared_ptr
    ,它简直是完美搭配,因为
    shared_ptr
    的删除器类型是擦除的。

  • 函数指针: 这是最传统、最简单的方式,适用于那些无状态、通用的清理函数。比如,如果你需要用

    free()
    来释放
    malloc()
    分配的内存,那么直接传入
    &free
    作为删除器就非常直观。它的优点是类型简单,开销小。但缺点也很明显,它不能捕获任何状态,所以如果你的清理逻辑依赖于某些运行时数据,函数指针就无能为力了。通常,我只会在清理操作非常标准且不依赖任何上下文时才考虑它。

总结一下,如果清理逻辑简单且无状态,函数指针或简单lambda就够了。如果需要捕获上下文或处理复杂状态,lambda通常是我的首选。而如果删除器本身需要高度复用、配置或者承载复杂逻辑,那么函数对象无疑是更强大的工具。

C++智能指针自定义删除器:避免常见错误与提升代码健壮性的实践

自定义删除器虽然强大,但用不好也容易踩坑。我在实际开发中就遇到过一些问题,总结下来,有些陷阱是需要特别留意的,同时也有一些实践能让代码更健壮。

常见陷阱:

  1. 删除器不处理
    nullptr
    :
    这是一个很常见的疏忽。智能指针在析构时,可能其内部管理的指针已经是
    nullptr
    (例如,它可能被
    release()
    了,或者在构造时就传入了
    nullptr
    )。你的自定义删除器必须能安全地处理
    nullptr
    ,否则可能导致

热门AI工具

更多
WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

Loomy
Loomy Hot

一款AI工具,主要用于科大讯飞发布的桌面级 AI 助理,比 OpenClaw 更易用、更安全!,适合需要提升相关任务效率的用户。

立刻MV
立刻MV Hot

立刻MV是一款AI文本写作工具,AI 音乐视频(MV)创作工具。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

码上飞
码上飞 Hot

一款基于自然语言的AI应用开发平台,可根据用户描述自动生成应用程序,帮助个人和团队快速完成应用原型与开发。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

相关专题

更多
resource是什么文件
resource是什么文件

Resource文件是一种特殊类型的文件,它通常用于存储应用程序或操作系统中的各种资源信息。它们在应用程序开发中起着关键作用,并在跨平台开发和国际化方面提供支持。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

320

2023.12.20

fclose函数的用法
fclose函数的用法

fclose是一个C语言和C++中的标准库函数,用于关闭一个已经打开的文件,是文件操作中非常重要的一个函数,用于将文件流与底层文件系统分离,释放相关的资源。更多关于fclose函数的相关问题,详情请看本专题下面的文章。php中文网欢迎大家前来学习。

842

2023.11.30

lambda表达式
lambda表达式

Lambda表达式是一种匿名函数的简洁表示方式,它可以在需要函数作为参数的地方使用,并提供了一种更简洁、更灵活的编码方式,其语法为“lambda 参数列表: 表达式”,参数列表是函数的参数,可以包含一个或多个参数,用逗号分隔,表达式是函数的执行体,用于定义函数的具体操作。本专题为大家提供lambda表达式相关的文章、下载、课程内容,供大家免费下载体验。

1151

2023.09.15

python lambda函数
python lambda函数

本专题整合了python lambda函数用法详解,阅读专题下面的文章了解更多详细内容。

345

2025.11.08

Python lambda详解
Python lambda详解

本专题整合了Python lambda函数相关教程,阅读下面的文章了解更多详细内容。

654

2026.01.05

C++ Lambda 表达式与函数式编程
C++ Lambda 表达式与函数式编程

深入讲解 C++ Lambda 表达式与函数式编程范式,涵盖 Lambda 基础语法与返回值推导、值捕获与引用捕获的区别与陷阱、初始化捕获(C++14 广义捕获)、泛型 Lambda(auto 参数)、mutable 关键字修改捕获变量、std::function 类型擦除与函数包装、std::bind 参数绑定(及其被 Lambda 替代的趋势)、Lambda 在 STL 算法(sort/transform/for_each/remo

829

2026.05.06

Java 函数式接口与 Lambda 进阶技巧
Java 函数式接口与 Lambda 进阶技巧

在 Java Stream 基础之上进一步深入函数式编程,涵盖 @FunctionalInterface 注解与自定义函数式接口设计、四种方法引用(静态/实例/对象/构造器)的使用场景、Lambda 中的 effectively final 变量捕获规则、函数组合(andThen/compose)与柯里化技巧、Comparator 链式比较器构建、CompletableFuture 异步函数式编排、Reactor / RxJava 响应

448

2026.05.08

C++ 智能指针与现代内存管理
C++ 智能指针与现代内存管理

深入讲解 C++ 现代内存管理的核心工具——智能指针,涵盖 unique_ptr 独占所有权语义、shared_ptr 引用计数机制与循环引用问题、weak_ptr 弱引用的应用场景、make_unique/make_shared 工厂函数的性能优势、自定义删除器的编写、RAII 资源管理思想的实践,以及从裸指针迁移到智能指针的重构策略,帮助开发者编写安全无泄漏的现代 C++ 代码。

379

2026.04.23

Postman身份认证怎么配置
Postman身份认证怎么配置

本专题从实际API接口调试场景出发,讲解Postman身份认证的完整配置流程,包括常见认证协议介绍、授权参数设置、访问令牌填写、OAuth 2.0授权及认证失败排查,帮助用户正确完成接口授权与访问验证。

60

2026.11.06

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn