当前位置: 首页 > news >正文

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

【智能指针】(上)目录

  • 前言:
  • ------------ 智能指针的使用 ------------
    • 1. 什么是智能指针?
    • 2. 常用智能指针的类型有哪些?
    • 3. 怎么使用智能指针?
    • 4. 为什么需要智能指针?
    • 5. 手动new/delete VS 智能指针 谁更胜一筹?
    • 6. 智能指针为什么这么吊?
      • 一、智能指针的核心本质
      • 二、智能指针的模拟实现
    • 7. 使用智能指针的好处有哪些?
    • 8. 关于智能指针有哪些注意事项?
  • ------------ 删除器 ------------
    • 1. 什么是删除器?
    • 2. 为什么要引入删除器?
    • 3. 删除器的本质是什么?
    • 4. 删除器怎么使用?
      • 一、与 unique_ptr 配合使用
      • 二、与 shared_ptr 配合使用
    • 5. unique_ptr与shared_ptr使用删除器的差异是什么?
    • 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 管理某块内存,不允许拷贝,只能移动

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

    #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 时自动释放

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

    #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 互相引用导致计数无法归零)

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

    #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(独占所有权)

#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(共享所有权)

#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;  // 引用计数 = 2shared_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(弱引用,解决循环引用)

#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的引用计数为1auto 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未执行,会造成内存泄漏(已分配的内存无法回收,直到程序结束)

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


2. 防止重复释放

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

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


3. 应对异常安全

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

    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,代码会变得冗余、难维护

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

#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

代码可简化为:

#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解决问题:传统手动管理资源的痛点

void func()
{// 1. 获取资源(动态内存)int* ptr = new int[10];// 2. 业务逻辑(可能抛异常、提前返回)if (someCondition){return; // 直接返回,导致 ptr 未释放 → 内存泄漏}// 3. 释放资源(若流程未被打断才会执行)delete[] ptr;
}

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

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 → 自动释放内存

二、智能指针的模拟实现

#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:业务逻辑:输入并调用 Divideint 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 栈内存的未定义行为
    /*------------- 自定义删除器:释放文件句柄 -------------*/
    void deleteFile(FILE* fp)
    {fclose(fp);
    }shared_ptr<FILE> p(fopen("test.txt", "w"),deleteFile // 析构时调用 deleteFile 释放资源
    );
    

(2)数组特化支持

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

    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))),工厂函数更安全(避免内存泄漏风险)且高效

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

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

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

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

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

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

#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会共享对象所有权,引用计数加1shared_ptr<Date> sp2(sp1); //此时引用计数变为2 //3.2:再次拷贝,引用计数变为3shared_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. 函数指针作为删除器

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

在这里插入图片描述

2. 仿函数作为删除器

  • 仿函数是重载了 operator() 的类,适合需要复用或带状态的删除逻辑
# 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 表达式简洁灵活,适合临时的简单释放逻辑
#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和5lambda_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. 函数指针作为删除器

/*---------------------注意:定义“资源类 + 函数指针删除器”同上(这里省略了)---------------------*/int main()
{// 创建shared_ptr智能指针,管理包含3个Resource对象的动态数组shared_ptr<Resource> sp(new Resource[3]{ 1, 2, 3 },  // 动态数组:用new[]创建3个Resource对象,ID为1、2、3ArrayDeleter<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. 仿函数作为删除器

/*---------------------注意:定义“仿函数删除器”同上(这里省略了)---------------------*/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 表达式作为删除器

/*---------------------注意:定义“资源类”同上(这里省略了)---------------------*/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;
}

在这里插入图片描述


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

#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_ptrshared_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),取决于场景的复杂度和复用需求。

在这里插入图片描述

http://www.dtcms.com/a/447044.html

相关文章:

  • 网站域名注册哪个好重庆seo优化效果好
  • 漳州微网站建设公司哪家好番禺网站排名优化公司
  • 帮人做网站好挣吗看房子建设进度的网站
  • 做网站报价出名的先注册域名后建设网站可以吗
  • JVM自动内存管理
  • 五里店网站建设免费申请qq号注册新账号
  • 网站字体大小是多少合适园林景观设计效果图
  • Oracle 11g R2 物理冷备
  • 公司网站本地如何弄网站的建设主题
  • 搜索引擎网站优化推广电脑做网站服务器
  • Scade One 图形建模 - 数组操作算符
  • [工作流节点13] 发送邮件节点配置与邮件模板技巧 —— 从基础通知到智能邮件自动化
  • 河池网站制作dns服务器 域名不存在时 跳转到指定网站
  • 做网站那个程序好高端网站建设好处
  • 洞察人心,构建未来:INFJ性格在互联网产品与设计领域的职业优势
  • 忆达城市建设游戏网站wordpress升级文章编辑器
  • audio 之 BtHelper
  • 企业网站建设版本用动物做logo的旅游网站
  • 正常开发一个网站需要多少钱广告公司用的什么软件
  • 阿里巴巴网站被关闭了要怎么做网站开发需求分析参考文献
  • php 新闻类网站怎么做济宁百度推广电话
  • 如何看一个网站是谁做的如何做一款服装网站
  • wordpress 添加网页电商seo是什么意思
  • telnet server enable 概念及题目
  • jsp网站开发的环境配置过程一般网站 广告
  • 河南快速网站备案网络营销的特点有即时性
  • 有网站建设的虚拟主机管理系统湖南郴州市
  • Hutool工具的引用和使用
  • ac86u做网站服务器app制作教程培训
  • Python匿名函数与内联函数完全指南:从基础到高阶应用