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 <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
内部的类型擦除机制。

GentleAI
GentleAI

GentleAI是一个高效的AI工作平台,为普通人提供智能计算、简单易用的界面和专业技术支持。让人工智能服务每一个人。

下载
#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工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

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

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

182

2023.12.20

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

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

344

2023.11.30

lambda表达式
lambda表达式

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

215

2023.09.15

python lambda函数
python lambda函数

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

193

2025.11.08

Python lambda详解
Python lambda详解

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

61

2026.01.05

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

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

447

2023.07.18

堆和栈区别
堆和栈区别

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

606

2023.08.10

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

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

891

2024.01.03

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

26

2026.03.13

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 6万人学习

ASP 教程
ASP 教程

共34课时 | 5.9万人学习

Vue3.x 工具篇--十天技能课堂
Vue3.x 工具篇--十天技能课堂

共26课时 | 1.6万人学习

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

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