《C++进阶之C++11》【智能指针】(上)

【智能指针】(上)目录

  • 前言:
  • [------------ 智能指针的使用 ------------](#------------ 智能指针的使用 ------------)
    • [1. 什么是智能指针?](#1. 什么是智能指针?)
    • [2. 常用智能指针的类型有哪些?](#2. 常用智能指针的类型有哪些?)
    • [3. 怎么使用智能指针?](#3. 怎么使用智能指针?)
    • [4. 为什么需要智能指针?](#4. 为什么需要智能指针?)
    • [5. 手动new/delete VS 智能指针 谁更胜一筹?](#5. 手动new/delete VS 智能指针 谁更胜一筹?)
    • [6. 智能指针为什么这么吊?](#6. 智能指针为什么这么吊?)
    • [7. 使用智能指针的好处有哪些?](#7. 使用智能指针的好处有哪些?)
    • [8. 关于智能指针有哪些注意事项?](#8. 关于智能指针有哪些注意事项?)
  • [------------ 删除器 ------------](#------------ 删除器 ------------)
    • [1. 什么是删除器?](#1. 什么是删除器?)
    • [2. 为什么要引入删除器?](#2. 为什么要引入删除器?)
    • [3. 删除器的本质是什么?](#3. 删除器的本质是什么?)
    • [4. 删除器怎么使用?](#4. 删除器怎么使用?)
      • [一、与 unique_ptr 配合使用](#一、与 unique_ptr 配合使用)
      • [二、与 shared_ptr 配合使用](#二、与 shared_ptr 配合使用)
    • [5. unique_ptr与shared_ptr使用删除器的差异是什么?](#5. unique_ptr与shared_ptr使用删除器的差异是什么?)
    • [6. 使用删除器的需要注意什么?](#6. 使用删除器的需要注意什么?)

往期《C++初阶》回顾:

《C++初阶》目录导航


往期《C++进阶》回顾:

/------------ 继承多态 ------------/
【普通类/模板类的继承 + 父类&子类的转换 + 继承的作用域 + 子类的默认成员函数】
【final + 继承与友元 + 继承与静态成员 + 继承模型 + 继承和组合】
【多态:概念 + 实现 + 拓展 + 原理】

/------------ STL ------------/
【二叉搜索树】
【AVL树】
【红黑树】
【set/map 使用介绍】
【set/map 模拟实现】
【哈希表】
【unordered_set/unordered_map 使用介绍】
【unordered_set/unordered_map 模拟实现】

/------------ C++11 ------------/
【列表初始化 + 右值引用】
【移动语义 + 完美转发】
【可变参数模板 + emplace接口 + 新的类功能】
【lambda表达式 + 包装器】
【异常】

前言:

hi~ 小伙伴们大家好呀~,明天就是中秋节了啦🌕✧٩(ˊᗜˋ)و✧。

本来这篇博客鼠鼠是特意留到中秋当天单篇完结C++的,结果赶巧碰上 "不咕挑战赛" 要结束了 ------ 估计大家早就忘了鼠鼠也报名参加了这个比赛了吧!(´• ω •`;)

毕竟这一个月里,鼠鼠发博客的频率实在不算高,别说日更了,连三天一次的打卡都偶尔放鸽了,完全没有正经参赛的样子,哈哈~​~( ̄▽ ̄)ゞ

所以鼠鼠决定了,要在比赛结束前最后几个小时里,偷偷一口气连发两篇博客 "冲刺" 一下,来一个出奇不意~

嘘嘘,这个小秘密可别传出去哦,不然就没惊喜啦!​(((┏(; ̄▽ ̄)┛
言归正传,这次要跟大家好好聊聊的是 C++ 进阶内容里的最后一章 ------【智能指针】

这部分的知识点特别重要,内容也比较多,所以鼠鼠特意拆成了(上)、(下)两篇来写,每篇的字数大概在 1W 字左右,能把每个细节都讲清楚。

智能指针在 C++ 里绝对算是 "重头戏" (☉д⊙),不管是日常开发避坑,还是面试考点,它都是绕不开的内容,所以接下来的内容大家可得认真看呀~╰(▔∀▔)╯

------------ 智能指针的使用 ------------

1. 什么是智能指针?

智能指针 :是 C++ 标准库提供的一种封装了原始指针的类模板 ,核心作用是自动管理动态内存,避免手动 new/delete 导致的内存泄漏(如:异常抛出时忘记释放内存)或重复释放等问题。

  • 它的本质是利用RAII(资源获取即初始化)机制:将动态内存的生命周期与智能指针对象的生命周期绑定 ------ 当智能指针对象离开作用域时,其析构函数会自动调用 delete 释放所管理的内存

2. 常用智能指针的类型有哪些?

三大常用智能指针的类型:

  • std::unique_ptr

    • 基本特性独占所有权,同一时间只能有一个 unique_ptr 管理某块内存,不允许拷贝,只能移动

    • 适用场景:管理单个对象或数组,明确内存归属唯一的场景

    cpp 复制代码
    #include <memory>
    using namespace std;
            
    int main()
    {
        //1.管理单个int对象
        unique_ptr<int> ptr1(new int(10));
        //2.或用更安全的make_unique(C++14)
        auto ptr2 = make_unique<int>(20);
            
        //3.不允许拷贝(编译报错)
        // unique_ptr<int> ptr3 = ptr1; 
            
        //4.允许移动(所有权转移)
        unique_ptr<int> ptr4 = move(ptr1);
    } // 离开作用域时,ptr2、ptr4自动释放内存

  • std::shared_ptr

    • 基本特性共享所有权,通过引用计数 记录有多少个 shared_ptr 管理同一块内存,当计数为 0 时自动释放

    • 适用场景:需要多个指针共享同一资源(如:容器中存储的对象被多个地方引用)

    cpp 复制代码
    #include <memory>
    using namespace std;
            
    int main()
    {
        auto ptr1 = make_shared<int>(30);
        shared_ptr<int> ptr2 = ptr1; // 引用计数变为2
            
        { //使用大括号定义了一个 "临时代码块"  ---> 定义了一个局部作用域
            shared_ptr<int> ptr3 = ptr1; // 引用计数变为3
        } // ptr3销毁,引用计数变回2
            
    } // ptr1、ptr2销毁,引用计数变为0,内存释放

  • std::weak_ptr

    • 基本特性弱引用,不增加引用计数,用于解决 shared_ptr 的循环引用问题(两个 shared_ptr 互相引用导致计数无法归零)

    • 适用场景:作为观察者关联共享资源,不参与所有权管理

    cpp 复制代码
    #include <memory>
    using namespace std;
            
    struct Node
    {
        weak_ptr<Node> next; // 用weak_ptr避免循环引用
    };
            
    int main()
    {
        auto node1 = make_shared<Node>();
        auto node2 = make_shared<Node>();
            
        node1->next = node2; // weak_ptr不增加计数
        node2->next = node1;
    } // 计数正常归零,内存释放

总结:

  • 独占资源unique_ptr ---> 适合 "一对一" 的场景
  • 共享资源shared_ptr ---> 适合 "多对一" 的场景
  • 观察资源weak_ptr ---> 解决循环引用

3. 怎么使用智能指针?

使用 C++ 智能指针的核心是利用其自动管理内存 的特性,避免手动 new/delete 导致的问题。

以下是三种常用智能指针的具体使用方法和场景:


一、std::unique_ptr(独占所有权)

cpp 复制代码
#include <iostream>
#include <memory>  // 需包含智能指针头文件
using namespace std;

int main() 
{
    /*--------------- 创建 unique_ptr(管理单个对象)---------------*/
    //方式1:直接绑定 new 分配的内存(不推荐,存在异常安全风险)
    unique_ptr<int> ptr1(new int(10));

    //方式2:用 make_unique 创建(C++14 推荐,更安全)
    auto ptr2 = make_unique<int>(20);  // 自动推导类型

    /*--------------- 访问所管理的对象(同普通指针)---------------*/
    *ptr2 = 30;             // 修改值
    cout << *ptr2 << endl;  // 输出:30

    /*--------------- 转移所有权(只能用 move,原指针会变为空)---------------*/
    unique_ptr<int> ptr3 = move(ptr2);  //注意:ptr2 不再拥有内存
    if (ptr2 == nullptr) 
    {
        cout << "ptr2 已为空" << endl;
    }

    /*--------------- 管理动态数组(需指定数组类型)---------------*/
    unique_ptr<int[]> arr_ptr = make_unique<int[]>(5);  // 5个int的数组
    arr_ptr[0] = 1;                                     // 数组访问
}
// 离开作用域时,所有 unique_ptr 自动释放内存(无需手动 delete)

二、std::shared_ptr(共享所有权)

cpp 复制代码
#include <iostream>
#include <memory>
using namespace std;

int main()
{
    //1.创建 shared_ptr(推荐用 make_shared)
    auto ptr1 = make_shared<int>(100);  // 引用计数 = 1

    //2.共享所有权(拷贝指针时,引用计数增加)
    shared_ptr<int> ptr2 = ptr1;  // 引用计数 = 2
    shared_ptr<int> ptr3 = ptr2;  // 引用计数 = 3

    //3.访问对象(同普通指针)
    *ptr3 = 200;
    cout << *ptr1 << endl;  // 输出:200(所有指针指向同一内存)

    //4.查看引用计数(use_count() 仅用于调试)
    cout << "计数:" << ptr1.use_count() << endl;  // 输出:3

    //5.局部作用域演示计数变化
    {
        shared_ptr<int> ptr4 = ptr1;  // 计数 = 4
    }  // ptr4 销毁,计数 = 3

    //6.手动释放(重置指针,计数减少)
    ptr2.reset();  // ptr2 不再指向内存,计数 = 2
}
// ptr1、ptr3 销毁,计数 = 0 → 内存自动释放

三、std::weak_ptr(弱引用,解决循环引用)

cpp 复制代码
#include <iostream>
#include <memory>  // 包含智能指针所需的头文件
using namespace std;

// 定义链表节点结构
// 场景:链表节点之间可能互相引用,容易引发shared_ptr的循环引用问题
struct Node
{
    int value;               // 节点存储的值
    weak_ptr<Node> next;     // 指向链表下一个节点的弱指针
    //关键:使用weak_ptr而非shared_ptr,避免循环引用
};

int main()
{
    //1.创建两个共享指针,分别管理两个Node对象
    auto node1 = make_shared<Node>();  // node1的引用计数为1
    auto node2 = make_shared<Node>();  // node2的引用计数为1
    //注意: make_shared是创建shared_ptr的推荐方式,安全且高效
   

    //2.构建节点间的互相引用关系
    node1->next = node2;  // weak_ptr接收shared_ptr时,不增加node2的引用计数(仍为1)
    node2->next = node1;  // 同理,node1的引用计数仍为1
    //注意:若此处用shared_ptr,会导致引用计数循环增加,无法归零

    //3.1:temp是有效的shared_ptr,说明node2仍存在
    if (auto temp = node1->next.lock()) //注意:访问weak_ptr指向的对象:必须通过lock()方法转换为shared_ptr
    {
        cout << "node2 有效" << endl;
    }
    //3.2:若node2已被释放,进入此分支
    else
    {
        cout << "node2 已释放" << endl;
    }
    /* 说明:lock()的作用:检查被引用的对象是否还存在
    *     1. 若存在:返回一个指向该对象的shared_ptr(此时引用计数临时+1)
    *     2. 若已释放:返回空的shared_ptr 
    */

}  // main函数结束,局部变量node1和node2开始销毁
   // 1. node2的引用计数从1减为0 → 其管理的Node对象被释放
   // 2. node1的引用计数从1减为0 → 其管理的Node对象被释放
   // 3. 由于使用weak_ptr,没有循环引用,所有内存正常释放(无内存泄漏)

4. 为什么需要智能指针?

在 C++ 中,智能指针的出现主要是为了:解决手动管理动态内存时容易出现的问题,其核心价值在于自动管理内存生命周期,避免内存相关的 bug。


具体来说,需要智能指针的原因可以从以下几个方面理解:

1. 避免内存泄漏

  • 手动管理动态内存(使用new分配、delete释放)时,若因逻辑疏漏导致delete未执行,会造成内存泄漏(已分配的内存无法回收,直到程序结束)

    cpp 复制代码
    void func() 
    {
        int* ptr = new int(10); // 分配内存
        
        if (someCondition) 
        {
            return; // 提前返回,导致后续的delete未执行
        }
        
        delete ptr; // 若if条件成立,此行不会执行,内存泄漏
    }
  • 智能指针会在自身生命周期结束时(如:超出作用域、被销毁)自动调用delete,无论程序执行路径如何(即使有提前返回、异常抛出等),都能保证内存被释放


2. 防止重复释放

  • 手动释放内存时,若对同一块内存多次调用delete,会导致未定义行为(程序崩溃、数据损坏等)

    cpp 复制代码
    void func() 
    {
        int* ptr1 = new int(10);
        
        int* ptr2 = ptr; // 两个指针指向同一块内存
        
        delete ptr1;
        delete ptr2; // 重复释放,行为未定义
    }
  • 智能指针通过引用计数等机制追踪内存的引用情况,只有当最后一个引用它的智能指针被销毁时,才会真正释放内存,避免重复释放


3. 应对异常安全

  • 当程序抛出异常时,手动管理的内存可能因delete语句被跳过而泄漏

    cpp 复制代码
    void func() 
    {
        int* ptr = new int(10);
    
        try 
        {
            someOperation(); // 若此函数抛出异常
        }
        catch (...) 
        {
            // 如果发生了异常:且未在catch中手动释放ptr,内存泄漏
            throw;
        }
    
        delete ptr;  // 如果未发生了异常:ptr指向的资源将在这里释放 
    }
  • 智能指针的析构函数会在异常发生时被自动调用(C++ 的栈展开机制),确保内存释放,无需手动在异常处理中额外处理


4. 简化内存管理逻辑

  • 复杂程序中,动态内存的所有权可能在多个函数、对象之间传递,手动追踪所有权并确定delete的时机非常困难

  • 智能指针通过明确的所有权语义 (如:unique_ptr的独占所有权、shared_ptr的共享所有权),简化了内存管理的逻辑,降低了人为出错的概率


总结:

C++ 没有垃圾回收机制,动态内存的分配与释放完全依赖程序员手动控制,这使得内存管理成为 C++ 开发中的常见痛点(内存泄漏重复释放异常安全等)

智能指针通过封装原始指针,在编译期和运行期自动处理内存的释放时机,本质上是 用对象管理资源 :"资源获取即初始化"(RAII:Resource Acquisition Is Initialization)思想的体现,大幅提升了代码的安全性和可维护性


5. 手动new/delete VS 智能指针 谁更胜一筹?

手动内存管理的泄露痛点:

当代码中混合使用 new动态分配内存异常抛出 时,若异常触发导致 delete 未执行,会直接引发 内存泄漏

更复杂的是:

  • 多个 new 操作可能各自抛异常(如:内存不足)
  • 业务逻辑(如:Divide 函数)也可能抛异常
    • 手动处理这些情况需要嵌套大量 try/catch,代码会变得冗余、难维护

代码示例:手动内存管理的困境

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

//1. 业务函数:除法运算,除数为0时抛异常
double Divide(int a, int b)
{
    if (b == 0)
    {
        // 抛C风格字符串异常
        throw "Divide by zero condition!";
    }
    return static_cast<double>(a) / b;
}

//2. 演示函数:手动管理内存 + 异常处理的复杂逻辑
void Func()
{
    //2.1:动态分配两个数组
    int* array1 = new int[10];
    int* array2 = new int[10]; // 若内存不足,此行抛异常,导致array1泄漏

    //2.2:包裹可能出现异常的程序
    try
    {
        //1.定义并输入两个int类型的操作数
        int a, b;
        cin >> a >> b;

        //2.输出运算结果
        cout << Divide(a, b) << endl;
    }
    //2.3:捕获所有异常(但不处理,仅释放内存后重新抛出)
    catch (...)
    {
        //1.输出报错信息
        cout << "delete [] " << array1 << endl;
        cout << "delete [] " << array2 << endl;

        //2.释放内存
        delete[] array1;
        delete[] array2;

        //3.重新抛出异常,交给上层处理
        throw;
    }

    //2.3:冗余的防御性释放(永远不会执行)
    cout << "delete [] " << array1 << endl;
    delete[] array1;

    cout << "delete [] " << array2 << endl;
    delete[] array2;
    /* 说明:
    *    1. 若try块内无异常,流程走到这里才会释放
    *    2. 但如果try内抛异常,会进入catch,导致此行被跳过 → 无意义
    */
}

//3. 主函数:调用Func并处理异常
int main()
{
    //3.1:包裹可能出现异常的程序
    try
    {
        Func();
    }


    //3.2:捕获C风格字符串异常
    catch (const char* errmsg)
    {
        cout << errmsg << endl;
    }
    //3.3:捕获标准异常
    catch (const exception& e)
    {
        cout << e.what() << endl;
    }
    //3.4:捕获未知异常
    catch (...)
    {
        cout << "未知异常" << endl;
    }

    return 0;
}

手动new/delete的不足之处:

  • 内存泄漏风险:

    • array2 = new int[10] 抛异常,array1 未被释放 → 泄漏

    • try 块内抛异常,catch 外的 delete永远不会执行 → 泄漏

  • 异常处理冗余:

    需要嵌套 try/catch 确保每个 new 都被释放,代码复杂度随 new 数量指数级上升


智能指针的优雅处理:

通过 std::unique_ptr 管理动态内存,利用 RAII 自动释放,无需手动 delete

代码可简化为:

cpp 复制代码
#include <iostream>
#include <memory>    // 包含智能指针所需的头文件(unique_ptr、make_unique等)
#include <stdexcept> // 包含标准异常类(如:runtime_error)
using namespace std;

//1. 除法运算函数
double Divide(int a, int b)
{
    //情况一:除数是0抛出异常
    if (b == 0)
    {
        // 抛标准异常:使用std::runtime_error(继承自std::exception)
        throw runtime_error("Divide by zero condition!"); //注:相比C风格字符串异常,标准异常支持多态捕获,信息更规范
    }
    //情况二:除数不为0返回计算结果
    return static_cast<double>(a) / b; //注:转换为double类型后再除法,保证结果精度
}

//2. 演示智能指针自动管理内存的函数
void Func()
{
    //2.1:使用unique_ptr管理动态数组(int[]表示数组类型)
    unique_ptr<int[]> array1 = make_unique<int[]>(10);
    unique_ptr<int[]> array2 = make_unique<int[]>(10);

    /* 说明:make_unique<int[]>(10):创建包含10个int元素的动态数组,返回unique_ptr
    *    1. 优势1:无需手动调用new,避免忘记初始化的风险
    *    2. 优势2:离开作用域时自动调用delete[]释放内存(无论正常执行还是异常)
    */

    //2.2:包裹可能出现异常的程序
    try
    {
        //1.定义并输入两个int类型的操作数
        int len, time;
        cin >> len >> time;

        //2.调用Divide函数,可能抛出异常
        cout << Divide(len, time) << endl;
    }
    //2.3:捕获所有std::exception派生的异常(包括runtime_error)
    catch (const exception& e)
    {
        //1.输出异常信息(e.what()返回异常描述字符串)
        cout << "捕获异常: " << e.what() << endl;


        //2.重新抛出异常,让上层函数(如:main)继续处理
        throw;   //注意:此时array1和array2尚未释放,会在Func()函数结束时自动释放
    }
}  // Func()函数结束,局部变量array1和array2离开作用域
   // 自动调用unique_ptr的析构函数,执行delete[]释放动态数组
   // 无论是否发生异常,此过程都会执行,彻底避免内存泄漏


//3. 主函数
int main()
{
    //3.1:
    try
    {
        Func();
    }

    //3.2:捕获Func()中抛出的异常(包括重新抛出的异常)
    catch (const exception& e)
    {
        // 输出最终的异常处理信息
        cout << "主函数捕获: " << e.what() << endl;
    }
    return 0;
}

总结对比 :智能指针通过 RAII 机制 完美解决了手动内存管理与异常处理的冲突,是现代 C++ 开发的推荐实践。

方案 内存管理方式 异常处理复杂度 泄漏风险 代码简洁度
手动 new/delete 手动控制 高(需嵌套)
智能指针 RAII 自动管理 低(无需手动)

6. 智能指针为什么这么吊?

一、智能指针的核心本质

智能指针 (unique_ptr/shared_ptr)是 RALL思想 的典型实现,RALL 设计思想:用对象生命周期管理资源

  • RALL 是 Resource Acquisition Is Initialization 的缩写,中文译为 "资源获取即初始化"

它是一种管理动态资源的设计思想,核心逻辑是:

  • 资源获取:在对象构造时获取资源(如:内存、文件句柄、网络连接)

  • 资源持有:资源在对象的生命周期内始终有效

  • 资源释放:对象析构时自动释放资源(无需手动调用 delete/close 等)

通过绑定 "资源的生命周期""对象的生命周期",避免因手动管理失误(如:忘记释放、异常导致跳过释放)引发的资源泄漏。


RALL解决问题:传统手动管理资源的痛点

cpp 复制代码
void func()
{
       // 1. 获取资源(动态内存)
       int* ptr = new int[10];

       // 2. 业务逻辑(可能抛异常、提前返回)
       if (someCondition)
       {
           return; // 直接返回,导致 ptr 未释放 → 内存泄漏
       }

       // 3. 释放资源(若流程未被打断才会执行)
       delete[] ptr;
}

RALL如何解决:用对象封装资源,析构时自动释放

cpp 复制代码
class ArrayWrapper
{
private:
    int* ptr; // 管理的资源(动态内存)

public:
    //1.构造时获取资源(动态内存)
    ArrayWrapper() : ptr(new int[10]) {}

    //2.析构时释放资源(无论对象如何销毁,析构函数必执行)
    ~ArrayWrapper() { delete[] ptr; }

    //3.禁用拷贝(避免资源重复释放)
    ArrayWrapper(const ArrayWrapper&) = delete;
    ArrayWrapper& operator=(const ArrayWrapper&) = delete;

    //3.提供资源访问接口
    int& operator[](int idx) { return ptr[idx]; }
};

void func()
{
    // 构造对象 → 获取资源
    ArrayWrapper arr;

    // 业务逻辑(即使抛异常、提前返回,对象析构时会释放资源)
    if (someCondition)
    {
        return; // 析构 arr → 自动释放内存
    }

} // 离开作用域 → 析构 arr → 自动释放内存

二、智能指针的模拟实现

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

// ------------------------------
// 1. 模拟实现智能指针(RAII 思想)
//    目标:管理动态数组,自动释放内存
// ------------------------------
template<class T>
class SmartPtr
{
private:
    T* _ptr;  // 管理的动态数组指针

public:
    /*----------------------默认成员函数----------------------*/
    //1.实现:"构造函数"
    SmartPtr(T* ptr) //参数:ptr 是 new 分配的数组指针
        : _ptr(ptr)
    {
        // 构造时获取资源,无需额外操作(资源由外部 new 分配)
    }

    //2.实现:"析构函数"
    ~SmartPtr()
    {
        //2.1:显示释放的地址
        cout << "delete[] " << _ptr << endl;

        //2.2:自动释放动态数组
        delete[] _ptr;
    }

    /*----------------------重载运算符----------------------*/

    //1.重载 operator*:支持解引用(类似原始指针 *ptr)
    T& operator*()
    {
        return *_ptr;  // 返回数组首元素的引用(注意:数组解引用无意义,仅演示语法)
    }

    //2.重载 operator->:支持成员访问(类似原始指针 ptr->)
    T* operator->()
    {
        return _ptr;  // 返回原始指针
    }

    //3.重载 operator[]:支持数组访问(类似原始数组 ptr[i])
    T& operator[](size_t i)
    {
        return _ptr[i];  // 返回数组第 i 个元素的引用
    }
};

// ------------------------------
// 2. 业务函数:除法运算(可能抛异常)
// ------------------------------
double Divide(int a, int b)
{
    if (b == 0)
    {
        throw "Divide by zero condition!"; //抛 C 风格字符串异常
    }

    return static_cast<double>(a) / b;    //正常除法(转换为 double 避免整数截断)
}

// ------------------------------
// 3. 演示函数:用模拟智能指针管理资源
// ------------------------------
void Func()
{
    //3.1:用 SmartPtr 管理动态数组(长度为 10)
    SmartPtr<int> sp1 = new int[10];
    SmartPtr<int> sp2 = new int[10];

    //3.2:初始化数组元素
    for (size_t i = 0; i < 10; i++)
    {
        sp1[i] = i;  // 通过 operator[] 访问数组
        sp2[i] = i;  // 通过 operator[] 访问数组
    }

    //3.3:业务逻辑:输入并调用 Divide
    int len, time;
    cin >> len >> time;
    cout << Divide(len, time) << endl;
}

// ------------------------------
// 4. 主函数:调用 Func 并处理异常
// ------------------------------
int main()
{
    try
    {
        Func();  // 调用可能抛异常的函数
    }

    // 捕获 C 风格字符串异常
    catch (const char* errmsg)
    {
        cout << errmsg << endl;
    }
    // 捕获标准异常(若有)
    catch (const exception& e)
    {
        cout << e.what() << endl;
    }
    // 捕获未知异常
    catch (...)
    {
        cout << "未知异常" << endl;
    }

    return 0;
}

7. 使用智能指针的好处有哪些?

智能指针的核心优势:

  1. 自动释放 :无需手动调用 delete,即使程序因异常跳转,也能保证内存被释放
  2. 避免泄漏 :解决了 "忘记释放"、"重复释放"、"释放后继续使用" 等常见问题
  3. 清晰语义 :通过 unique_ptr(独占)和 shared_ptr(共享)清晰表达资源的管理方式

8. 关于智能指针有哪些注意事项?

智能指针的通用设计细节:

(1)RAII 与资源释放

  • 智能指针的析构函数默认调用 delete 释放资源

  • 若管理的资源不是new分配的(如:文件句柄、网络连接),需自定义删除器

    • 注意:避免用智能指针管理非动态内存,否则会导致 delete 栈内存的未定义行为
    cpp 复制代码
    /*------------- 自定义删除器:释放文件句柄 -------------*/
    void deleteFile(FILE* fp)
    {
        fclose(fp);
    }
    
    shared_ptr<FILE> p(
        fopen("test.txt", "w"),
        deleteFile // 析构时调用 deleteFile 释放资源
    );

(2)数组特化支持

  • unique_ptr和shared_ptr可通过模板特化支持动态数组

    cpp 复制代码
    unique_ptr<int[]> arr1 = make_unique<int[]>(10); // 管理 10 个 int 的数组
    shared_ptr<int[]> arr2 = make_shared<int[]>(10); // 同理
    
    arr1[0] = 100; // 支持数组下标访问

(3)推荐的创建方式 make_shared/make_unique

  • 相比直接new(如:shared_ptr<int>(new int(10))),工厂函数更安全(避免内存泄漏风险)且高效

    cpp 复制代码
    auto p1 = make_unique<int>(10); // 推荐
    
    auto p2 = unique_ptr<int>(new int(10)); // 不推荐(异常安全风险)

(4)空指针判断与类型安全

  • 支持operator bool:直接判断智能指针是否为空(管理资源)

    cpp 复制代码
    if (p1)  // 等价于 p1 != nullptr
    {
        cout << "p1 管理资源" << endl;
    }
  • 构造函数用explicit修饰:禁止普通指针隐式转换为智能指针,避免误操作

    cpp 复制代码
    // 编译报错(禁止隐式转换)
    shared_ptr<int> p = new int(10); 
    
    // 正确写法
    shared_ptr<int> p(new int(10)); 

代码案例:智能指针的使用细节

cpp 复制代码
#include <iostream>
#include <memory>  
using namespace std;

/*-----------------定义:"日期类"-----------------*/
struct Date
{
    int _year;  // 年
    int _month; // 月
    int _day;   // 日

    //1.实现:"构造函数"
    Date(int year = 1, int month = 1, int day = 1)
        : _year(year)
        , _month(month)
        , _day(day)
    { }

    //2.实现:"析构函数"
    ~Date()
    {
        //当对象被销毁时输出提示信息,便于追踪智能指针的释放行为
        cout << "~Date()" << endl; 
    }
};


int main()
{
    /*----------------------------------------- auto_ptr -----------------------------------------*/
    //1.创建auto_ptr智能指针,管理一个Date对象
    auto_ptr<Date> ap1(new Date);  

    /* 说明:auto_ptr(C++98 遗留,已废弃)
    *     1. 特性:管理权唯一,拷贝时会转移所有权
    *     2. 注意:C++11后已被弃用,不推荐使用,存在潜在风险
    */
 
    auto_ptr<Date> ap2(ap1);
    /*
    *  1. 拷贝auto_ptr会导致所有权转移:ap1失去管理权,ap2获得管理权
    *  2. 此时ap1变为悬空指针,指向已被转移的对象
    *
    *  -------------------------------------------------------------------
    *
    *  3. 危险操作:ap1已空悬,访问其指向的对象会导致未定义行为(可能崩溃)
    *  4. 这里注释掉以避免运行时错误
    */
    // ap1->_year++;              

    /*----------------------------------------- unique_ptr -----------------------------------------*/
    //2.创建unique_ptr智能指针,管理一个Date对象
    unique_ptr<Date> up1(new Date);  

    /* 说明:unique_ptr(C++11 推荐,独占所有权)
    *     1. 特性:严格独占所管理的对象,禁止拷贝,只支持移动
    *     2. 适用场景:需要唯一管理资源,避免共享的情况
    */


    // unique_ptr<Date> up2(up1);  
    /*
    *  1. 禁止拷贝:unique_ptr的拷贝构造函数被删除,上面的代码会编译报错
    *  2. 这是设计上的保护,防止多个unique_ptr管理同一个对象
    *
    *  -------------------------------------------------------------------
    *
    *  3. 支持移动语义:通过std::move()转移所有权
    *  4. 转移后,up1变为悬空指针,up3获得对象的管理权
    */
    unique_ptr<Date> up3(move(up1));


    /*----------------------------------------- shared_ptr -----------------------------------------*/

    //3. 创建shared_ptr智能指针,管理一个Date对象
    shared_ptr<Date> sp1(new Date);  

    /* 说明:shared_ptr(C++11 推荐,共享所有权)
    *     1. 特性:允许多个shared_ptr共享同一个对象的所有权
    *     2. 内部通过引用计数(reference count)跟踪对象被多少指针共享
    *     3. 当引用计数为0时,自动释放所管理的对象
    */

    //3.1:支持拷贝:新的shared_ptr会共享对象所有权,引用计数加1
    shared_ptr<Date> sp2(sp1); //此时引用计数变为2 

    //3.2:再次拷贝,引用计数变为3
    shared_ptr<Date> sp3(sp2);

    //3.3:输出当前引用计数 ---> 注:use_count()方法返回当前共享该对象的shared_ptr数量
    cout << "sp1的引用计数: " << sp1.use_count() << endl;  

    //3.4:通过智能指针访问对象成员(与普通指针用法类似)
    sp1->_year++;  

    //3.5:验证所有共享指针都指向同一个对象 --> 输出结果均为2,证明它们共享同一个Date对象
    cout << "sp1指向的年份: " << sp1->_year << endl;
    cout << "sp2指向的年份: " << sp2->_year << endl;
    cout << "sp3指向的年份: " << sp3->_year << endl;

    //3.6:支持移动语义:通过std::move()转移所有权
    shared_ptr<Date> sp4(move(sp1));
    /* 注意:
    *   1. 转移后,sp1变为悬空指针,sp4获得对象的管理权
    *   2. 注意:移动操作不会增加引用计数,只是转移管理权限
    */

    // 程序结束时,所有智能指针会自动释放所管理的对象
    // 对于shared_ptr,当最后一个管理对象的指针销毁时,才会调用Date的析构函数
    // 此处会输出一次"~Date()",因为所有shared_ptr共享的是同一个对象
    // unique_ptr和auto_ptr管理的对象也会在此处自动释放
    return 0;
}

------------ 删除器 ------------

1. 什么是删除器?

删除器 :是智能指针(如:unique_ptrshared_ptr)用于释放所管理资源的一个重要机制。

  • 它的核心作用是定义资源的释放方式,确保智能指针在生命周期结束时,能正确、安全地回收所管理的资源
  • 它允许自定义资源释放的方式,而不仅限于 delete 操作,这使得智能指针可以管理各种类型的资源,如:文件句柄、网络连接、锁等

2. 为什么要引入删除器?

智能指针默认使用 delete 运算符释放资源,但实际场景中存在很多特殊情况:

  • 动态数组:需要用 delete[] 释放(而非 delete
  • 非内存资源(如:文件句柄 FILE*、网络套接字 socket):需要调用特定函数释放(如:fcloseclosesocket
  • 自定义的资源释放逻辑(如:日志记录、状态清理等)

此时,默认的 delete 无法满足需求,必须通过自定义删除器来指定资源的释放方式。

总结 :使用删除器可以管理非 new 分配的资源(如:malloc、fopen 分配的资源)

3. 删除器的本质是什么?

删除器本质是一个 "可调用对象",它可以是:

  • 普通函数
  • 仿函数(重载 operator() 的类对象)
  • lambda 表达式
  • 函数指针
  • std::function包装的函数
  • ............

当智能指针需要释放其管理的资源时,会调用这个删除器而不是简单的 delete

4. 删除器怎么使用?

一、与 unique_ptr 配合使用

unique_ptr 是独占所有权的智能指针,其模板参数必须显式指定删除器的类型(删除器类型是 unique_ptr 类型的一部分)


1. 函数指针作为删除器

cpp 复制代码
#include <memory>
#include <iostream>
using namespace std;  

/*------------------------ 定义一个资源类 ------------------------*/
struct Resource
{
    int id;  // 资源的唯一标识,用于区分不同的Resource对象

    Resource(int i) : id(i) { cout << "Resource " << id << " 创建\n"; }
    ~Resource() { cout << "Resource " << id << " 被默认释放\n"; }
};

/*------------------------ 定义"函数指针"删除器 ------------------------*/
template<class T>
void ArrayDeleter(T* ptr) //注意:数组中元素的类型(此处为Resource)
{
    if (ptr) //安全检查:避免对空指针执行释放操作
    {
        //1.显示使用自定义删除器释放数组的提示
        cout << "用 ArrayDeleter 释放数组\n"; 

        //2.用delete[]释放动态数组(与new[]配对)
        delete[] ptr;  
    }
}

int main()
{
    // 创建unique_ptr智能指针,管理一个包含3个Resource对象的动态数组
    unique_ptr<Resource, void(*)(Resource*)> up(
        new Resource[3]{ 1, 2, 3 },  // 动态数组:用new[]创建3个Resource对象,初始化ID为1、2、3
        ArrayDeleter<Resource>       // 绑定删除器:指定用ArrayDeleter<Resource>函数释放资源
    );
    //注意:unique_ptr的模板参数必须显式包含删除器类型,因为删除器类型是unique_ptr类型的一部分
    
    /* 模板参数说明:<资源类型, 删除器类型(函数指针)>
    *        1. 第一个参数 Resource:表示智能指针管理的资源类型(数组元素类型)
    *        2. 第二个参数 void(*)(Resource*):表示删除器的类型(函数指针类型,指向接收Resource*参数、返回void的函数)
    *
    */
}
/*
*   main函数结束,up的生命周期结束
*   此时unique_ptr会自动调用绑定的删除器(ArrayDeleter<Resource>)释放管理的动态数组
*/

2. 仿函数作为删除器

  • 仿函数是重载了 operator() 的类,适合需要复用或带状态的删除逻辑
cpp 复制代码
# define _CRT_SECURE_NO_WARNINGS
#include <iostream>   
#include <memory>     
using namespace std; 

/*------------------------ 定义"仿函数"删除器 ------------------------*/
class FileDeleter
{
public:
    void operator()(FILE* fp) const //参数fp:指向FILE类型的指针(文件句柄),即需要释放的资源
    {
        if (fp)  // 安全检查:避免对空指针执行操作(fclose(nullptr)可能导致崩溃)
        {
            //1.显示当前关闭的文件句柄(便于追踪资源释放)
            cout << "用 FileDeleter 关闭文件\n";

            //2.调用标准库函数fclose关闭文件,释放系统资源
            fclose(fp); //替代手动调用fclose,由智能指针自动触发
        }
    }
};


int main()
{
    // 创建unique_ptr智能指针,管理文件句柄(FILE*类型资源)
    unique_ptr<FILE, FileDeleter> up_file(
        fopen("test.txt", "r"),  // 资源初始化:调用fopen打开文件,返回FILE*句柄
        FileDeleter()            // 绑定删除器:传入FileDeleter的实例(可省略,因unique_ptr会默认构造)
    ); //注:"r"表示只读模式,若文件不存在则fopen返回nullptr
    // 注意:若fopen失败(返回nullptr),删除器也会安全处理(因operator()内有if(fp)判断)

    /* 模板参数说明:<资源类型, 仿函数类型>
    *        1. 第一个参数 FILE:表示智能指针管理的资源类型(文件句柄类型)
    *        2. 第二个参数 FileDeleter:表示删除器的类型(仿函数类)
    */
    

    // 当up_file生命周期结束时(main函数退出),发生以下过程:
    // 1. unique_ptr自动调用绑定的删除器(FileDeleter的operator())
    // 2. 删除器调用fclose关闭文件,确保资源不泄漏
    // 无需手动调用fclose,避免了"忘记关闭文件"导致的资源泄漏

    return 0;
}

3. lambda 表达式作为删除器

  • lambda 表达式简洁灵活,适合临时的简单释放逻辑
cpp 复制代码
#include <iostream>   
#include <memory>    
using namespace std;  

/*------------------------ 定义一个资源类 ------------------------*/
 struct Resource
 {
     int id;   // 资源的唯一标识,用于区分不同的Resource对象

     Resource(int i) : id(i) { cout << "Resource " << id << " 创建\n"; }
     ~Resource() { cout << "Resource " << id << " 被默认释放\n"; }
 };

int main()
{
    /*------------------------ 定义"lambda表达式"删除器 ------------------------*/
    auto lambda_deleter = [](Resource* ptr) { //lambda捕获列表为空([]),参数为Resource*(指向数组的指针)

        if (ptr)  // 安全检查:避免对空指针执行释放操作
        {
            //1.标识当前使用lambda删除器释放数组
            cout << "用 lambda 释放数组\n";

            //2.用delete[]释放动态数组(与new[]配对)
            delete[] ptr;
        }
    };

    // 创建unique_ptr智能指针,管理包含2个Resource对象的动态数组
    unique_ptr<Resource, decltype(lambda_deleter)> up(
        new Resource[2]{ 4, 5 },  // 动态数组:用new[]创建2个Resource对象,ID为4和5
        lambda_deleter            // 绑定删除器:指定用上面定义的lambda表达式释放资源
    );
    //注意:因lambda类型是编译器自动生成的匿名类型,必须用decltype推导

    /* 模板参数说明:<资源类型, decltype(lambda)>(通过 decltype 获取 lambda 类型)
    *        1. 第一个参数 Resource:表示智能指针管理的资源类型(数组元素类型)
    *        2. 第二个参数 decltype(lambda_deleter):通过decltype获取lambda的类型
    */



    /* 当up的生命周期结束时(main函数退出):
    *          1. unique_ptr自动调用绑定的lambda_deleter删除器
    *          2. lambda删除器先打印"用 lambda 释放数组"
    *          3. 再调用delete[]释放数组(触发元素析构,打印"被默认释放")
    *  整个过程无需手动释放,避免内存泄漏
    */
    return 0;
}

二、与 shared_ptr 配合使用

shared_ptr 是共享所有权的智能指针,其模板参数不需要包含删除器类型(删除器类型不影响 shared_ptr 本身的类型),只需在构造时传入删除器即可,这使得 shared_ptr 对删除器的使用更灵活


1. 函数指针作为删除器

cpp 复制代码
/*---------------------注意:定义"资源类 + 函数指针删除器"同上(这里省略了)---------------------*/

int main()
{
    // 创建shared_ptr智能指针,管理包含3个Resource对象的动态数组
    shared_ptr<Resource> sp(
        new Resource[3]{ 1, 2, 3 },  // 动态数组:用new[]创建3个Resource对象,ID为1、2、3
        ArrayDeleter<Resource>       // 绑定删除器:传入ArrayDeleter<Resource>函数作为释放逻辑
        
    );
    /* 注意:
    *    1. shared_ptr模板参数仅需指定资源类型(Resource),无需包含删除器类型
    *    2. shared_ptr的删除器在构造时传入,不影响智能指针类型
    */


    // shared_ptr的核心特性:支持共享所有权,可以创建多个shared_ptr共享同一资源(引用计数会自动增加)
    // 例如:
    // shared_ptr<Resource> sp2 = sp;  // 此时引用计数为2

    /* 当最后一个持有该资源的shared_ptr(如sp、sp2等)生命周期结束时:
    *          1. 引用计数减为0,触发资源释放
    *          2. 自动调用绑定的ArrayDeleter<Resource>删除器
    *          3. 删除器执行:先打印释放提示,再用delete[]释放数组(调用元素析构函数)
    *  整个过程无需手动管理内存,彻底避免内存泄漏
    */


    return 0;
}

2. 仿函数作为删除器

cpp 复制代码
/*---------------------注意:定义"仿函数删除器"同上(这里省略了)---------------------*/


int main()
{
    // 创建shared_ptr智能指针,管理文件句柄(FILE*类型的资源)
    shared_ptr<FILE> sp_file(
        fopen("test.txt", "r"),  // 资源初始化:调用fopen打开文件
        FileDeleter()            // 绑定删除器:传入FileDeleter仿函数的实例
    ); 
    /* 注意事项:
    *     1. "r"表示只读模式,成功则返回FILE*指针,失败返回nullptr
    *     2. shared_ptr模板参数仅需指定资源类型(FILE),无需包含删除器类型
    *     2. shared_ptr构造时直接传入删除器,不影响智能指针类型
    *     3. 若fopen失败(返回nullptr),删除器会通过if(fp)判断跳过操作,避免错误
    */

    // shared_ptr支持共享所有权:多个shared_ptr可共享同一文件句柄
    // 例如:
    // shared_ptr<FILE> sp_file2 = sp_file;  // 引用计数+1


    /* 当最后一个持有该文件句柄的shared_ptr(如:sp_file、sp_file2)生命周期结束时:
    *          1. 引用计数减为0,触发资源释放
    *          2. 自动调用FileDeleter仿函数的operator()方法
    *          3. 仿函数内部调用fclose关闭文件,确保资源不泄漏
    *  无需手动调用fclose,避免"忘记关闭文件"导致的系统资源泄漏
    */

    return 0;
}

3. lambda 表达式作为删除器

cpp 复制代码
/*---------------------注意:定义"资源类"同上(这里省略了)---------------------*/

int main()
{
    // 创建shared_ptr智能指针,管理包含2个Resource对象的动态数组
    shared_ptr<Resource> sp(
        new Resource[2]{ 6, 7 },  // 动态数组:用new[]创建2个Resource对象,ID为6和7
        [](Resource* ptr)         // 直接传入lambda表达式作为删除器
        {       
            if (ptr) // 安全检查:避免对空指针执行释放操作
            {            
                //1.标识当前使用lambda删除器释放数组
                cout << "用 lambda 释放数组\n";

                //2.用delete[]释放动态数组(与new[]配对)
                delete[] ptr;
            }
        }
    );
    //注意:shared_ptr模板参数仅需指定资源类型(Resource),无需包含删除器类型

    // shared_ptr的特性:支持共享所有权:可以创建多个shared_ptr共享同一资源,引用计数会自动维护
    // 例如:
    // shared_ptr<Resource> sp2 = sp;  // 此时引用计数增加到2

    /* 当最后一个持有该资源的shared_ptr(如:sp、sp2等)生命周期结束时:
    *      1. 引用计数减少到0,触发资源释放机制
    *      2. 自动调用绑定的lambda表达式(删除器)
    *      3. lambda删除器执行流程:
    *               - 先打印"用 lambda 释放数组"
    *               - 再调用delete[]释放数组,此时会依次调用数组中每个元素的析构函数
    *               - 最终完成数组内存的释放,避免内存泄漏
    */

    // 优势:lambda表达式作为删除器无需提前定义,适合简单的释放逻辑,代码更紧凑
    return 0;
}

代码案例:删除器使用的总结

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <memory>  // 智能指针头文件
#include <cstdio>  // 文件操作相关函数
using namespace std;

/*---------------------------定义:"日期类"---------------------------*/
struct Date
{
    int _year;  // 年
    int _month; // 月
    int _day;   // 日

    //1.实现:"构造函数"
    Date(int year = 1, int month = 1, int day = 1)
        : _year(year)
        , _month(month)
        , _day(day)
    {
    }

    //2.实现:"析构函数"
    ~Date()
    {
        //当对象被销毁时输出提示信息,便于追踪智能指针的释放行为
        cout << "~Date()" << endl;
    }
};

/*---------------------------定义"函数指针"删除器:删除动态数组---------------------------*/
template<class T>
void DeleteArrayFunc(T* ptr)
{
    delete[] ptr;  // 使用delete[]释放动态数组,与new[]配对
}


/*---------------------------定义"仿函数"删除器:删除动态数组---------------------------*/
template<class T>
class DeleteArray
{
public:
    // 重载()运算符,使类的对象可以像函数一样被调用
    void operator()(T* ptr)
    {
        delete[] ptr;  // 使用delete[]释放动态数组
    }
};


/*---------------------------定义"仿函数"删除器:关闭文件句柄---------------------------*/
class Fclose
{
public:
    // 重载()运算符,接收FILE*类型的指针
    void operator()(FILE* ptr)
    {
        //1.显示关闭的文件指针
        cout << "fclose:" << ptr << endl;

        //2.调用标准库函数,关闭文件
        fclose(ptr);
    }
};


int main()
{
    /*----------------------------------- 案例一:智能指针管理"动态数组" -----------------------------------*/
    // unique_ptr<Date> up1(new Date[10]); 
    // shared_ptr<Date> sp1(new Date[10]); 
    /*  错误示例:直接用智能指针管理数组会导致内存泄漏
    *        1. 原因:智能指针默认使用delete释放资源,而动态数组需要用delete[]
    *        2. 所以:以上两行代码会编译通过,但运行时会产生未定义行为(内存泄漏或崩溃)
    */

    /*--------------- 解决方案1:利用智能指针的数组特化版本 --------------*/

    //注:unique_ptr和shared_ptr都提供了数组版本的特化,会自动使用delete[]
    unique_ptr<Date[]> up1(new Date[5]);  // 管理包含5个Date对象的动态数组
    shared_ptr<Date[]> sp1(new Date[5]);  // 数组特化版本,自动调用delete[]



    /*--------------- 解决方案2.1:自定义删除器(函数指针版本)--------------*/

    //1. unique_ptr需指定函数指针类型作为模板参数
    unique_ptr<Date, void(*)(Date*)> up2(new Date[5], DeleteArrayFunc<Date>);

    //2. shared_ptr对函数指针的支持更简洁,直接传入函数名即可
    shared_ptr<Date> sp2(new Date[5], DeleteArrayFunc<Date>);


    /*--------------- 解决方案2.2:自定义删除器(仿函数版本)--------------*/

    //1. unique_ptr需要在模板参数中显式指定删除器类型
    unique_ptr<Date, DeleteArray<Date>> up3(new Date[5], DeleteArray<Date>());

    //2. shared_ptr不需要在模板参数中指定删除器类型,直接在构造函数中传入即可
    shared_ptr<Date> sp3(new Date[5], DeleteArray<Date>());


    /*--------------- 解决方案2.3:自定义删除器(lambda表达式版本)--------------*/

    // 定义一个lambda表达式,用于释放动态数组
    auto delArrOBJ = [](Date* ptr) { delete[] ptr; };

    //1. unique_ptr需要用decltype获取lambda的类型作为模板参数
    unique_ptr<Date, decltype(delArrOBJ)> up4(new Date[5], delArrOBJ);

    //2. shared_ptr可以直接使用lambda作为删除器,无需指定类型
    shared_ptr<Date> sp4(new Date[5], delArrOBJ);


    /*----------------------------------- 案例二:智能指针管理"非内存资源" -----------------------------------*/

    //1.使用Fclose仿函数作为删除器
    shared_ptr<FILE> sp5(fopen("test.txt", "r"), Fclose());


    //2.使用lambda表达式作为删除器(功能与Fclose仿函数相同)
    shared_ptr<FILE> sp6(
        fopen("test.txt", "r")
        , [](FILE* ptr)
        {
            cout << "fclose:" << ptr << endl;  // 调试输出
            fclose(ptr);                       // 关闭文件
        }
    );

    /* 程序结束时,所有智能指针会自动调用对应的删除器释放资源:
    *          1. 数组特化版本:调用delete[]
    *          2. 自定义删除器版本调用:对应的函数/仿函数/lambda
    *          3. 文件指针会被正确关闭,避免资源泄漏
    */
    return 0;
}

5. unique_ptr与shared_ptr使用删除器的差异是什么?

总结unique_ptrshared_ptr 使用删除器的差异

特性 unique_ptr shared_ptr
模板参数是否包含删除器 是 (删除器类型是模板的一部分,如 unique_ptr<T, Deleter> 否 (删除器类型不影响 shared_ptr 类型,仅在构造时传入)
语法要求 必须显式指定删除器类型(或用 decltype 获取) 无需指定删除器类型,直接传入删除器对象即可
灵活性 同一 unique_ptr 类型只能绑定固定类型的删除器 同一 shared_ptr 类型可绑定不同删除器(只要释放逻辑兼容)
内存开销 若删除器是无状态的(如空仿函数),不增加额外开销 有状态删除器会增加大小 无论删除器类型,shared_ptr 本身大小固定 (通常为两个指针:资源指针 + 控制块指针)

6. 使用删除器的需要注意什么?

使用删除器的注意事项:

  1. 删除器的参数类型必须匹配:删除器接收的参数类型必须与智能指针管理的资源类型一致(如:管理 FILE* 资源,删除器必须接收 FILE*
  2. 避免删除空指针:删除器内部最好判断 ptr != nullptr,避免对空指针执行释放操作(虽然 delete nullptr 是安全的,但 fclose(nullptr) 可能崩溃)
  3. shared_ptr的删除器存储在控制块shared_ptr 的删除器与引用计数一起存在控制块中,因此即使删除器很大,也不会增加 shared_ptr 本身的大小
  4. 默认删除器的局限性:智能指针默认使用 delete 释放资源,因此管理动态数组非内存资源时,必须显式指定删除器,否则会导致未定义行为

总结:

删除器的使用核心根据资源类型,定义对应的释放逻辑,然后将其绑定到智能指针

具体选择哪种形式的删除器(函数指针/仿函数 /lambda),取决于场景的复杂度和复用需求。

相关推荐
guigu20122 小时前
C++ STL 深度解析:容器、迭代器与算法的协同作战
c++·嵌入式编程
Yupureki2 小时前
从零开始的C++学习生活 3:类和对象(中)
c语言·c++·学习·visual studio
十安_数学好题速析2 小时前
根式方程:结构联想巧用三角代换
笔记·学习·高考
励志不掉头发的内向程序员2 小时前
【Linux系列】并发世界的基石:透彻理解 Linux 进程 — 进程状态
linux·运维·服务器·开发语言·学习
弘毅 失败的 mian3 小时前
利用 VsCode + EIDE 进行嵌入式开发(保姆级教程)
经验分享·笔记·vscode·stm32·单片机·嵌入式硬件·eide
li星野3 小时前
打工人日报#20251002
笔记·程序人生·学习方法
知识分享小能手3 小时前
微信小程序入门学习教程,从入门到精通,WXSS样式处理语法基础(9)
前端·javascript·vscode·学习·微信小程序·小程序·vue
尘似鹤4 小时前
微信小程序学习(四)
学习·微信小程序
凤年徐4 小时前
【C++】string类
c语言·开发语言·c++