0

0

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

P粉602998670

P粉602998670

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

|

920人浏览过

|

来源于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 
    #include 
    #include  // 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 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 
    #include 
    #include  // For std::jthread example
    #include  // 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
            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 
    #include 
    #include  // 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 data(
            static_cast(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
实例都可以有不同的删除器,而它们的类型仍然是
std::shared_ptr
。这得益于
shared_ptr
内部的类型擦除机制。

Lessie AI
Lessie AI

一款定位为「People Search AI Agent」的AI搜索智能体

下载
#include 
#include 
#include 
#include  // 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
    std::shared_ptr 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 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
    ,否则可能导致

相关专题

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

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

149

2023.12.20

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

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

328

2023.11.30

lambda表达式
lambda表达式

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

204

2023.09.15

python lambda函数
python lambda函数

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

190

2025.11.08

Python lambda详解
Python lambda详解

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

49

2026.01.05

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

392

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

572

2023.08.10

class在c语言中的意思
class在c语言中的意思

在C语言中,"class" 是一个关键字,用于定义一个类。想了解更多class的相关内容,可以阅读本专题下面的文章。

465

2024.01.03

Java JVM 原理与性能调优实战
Java JVM 原理与性能调优实战

本专题系统讲解 Java 虚拟机(JVM)的核心工作原理与性能调优方法,包括 JVM 内存结构、对象创建与回收流程、垃圾回收器(Serial、CMS、G1、ZGC)对比分析、常见内存泄漏与性能瓶颈排查,以及 JVM 参数调优与监控工具(jstat、jmap、jvisualvm)的实战使用。通过真实案例,帮助学习者掌握 Java 应用在生产环境中的性能分析与优化能力。

0

2026.01.20

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
黑马云课堂mongodb实操视频教程
黑马云课堂mongodb实操视频教程

共11课时 | 3.1万人学习

DOM操作与实战案例
DOM操作与实战案例

共14课时 | 1.9万人学习

iOS应用UI控件开发基础视频
iOS应用UI控件开发基础视频

共148课时 | 31.4万人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号