【C++11】智能指针的使用
智能指针的使用
智能指针的使用
github地址
前言
在现代 C++ 开发中,资源管理 始终是绕不开的话题。随着项目规模不断扩大、内存模型愈发复杂,传统的 new/delete 手动管理方式极易导致内存泄漏、重复释放、异常路径未释放等问题,成为 C++ 程序最常见、也最难排查的 Bug 之一。
为了解决这一痛点,C++11 引入了更安全也更现代的资源管理机制——智能指针(Smart Pointer)。它们基于 RAII(资源获取即初始化) 思想,通过对象生命周期管理动态内存,使得 C++ 能够像带垃圾回收机制的语言一样安全地使用动态资源,同时仍保留手动控制的灵活性与高性能。
本篇博客将系统性介绍 C++11 提供的三种智能指针:
unique_ptr—— 独占所有权,零开销的 RAII 指针shared_ptr—— 共享所有权,引用计数逻辑透明可靠weak_ptr—— 弱引用,用于解决循环引用问题
同时还会通过大量代码示例,深入讲解:
- 智能指针的使用方式与底层原理
- 动态数组、非内存资源的管理
- 删除器(deleter)的实现与差异
- 智能指针常见误区与最佳实践
希望通过这篇文章,你能真正掌握智能指针的使用模式,从而写出更安全、更现代、更健壮的 C++ 代码。
一、什么是智能指针
智能指针:是 C++ 标准库提供的一种封装了原始指针的类模板,核心作用是自动管理动态内存,避免手动 new/delete 导致的内存泄漏(如:异常抛出时忘记释放内存)或重复释放等问题。
- 它的本质是利用 RAII(资源获取即初始化) 机制:将动态内存的生命周期与智能指针对象的生命周期绑定 —— 当智能指针对象离开作用域时,其析构函数会自动调用 delete 释放所管理的内存
二、 常用智能指针的类型有哪些?
1. std::unique_ptr
std::unique_ptr
- 基本特性:独占所有权,同一时间只能有一个 unique_ptr 管理某块内存,不允许拷贝,只能移动
- 适用场景:管理单个对象或数组,明确内存归属唯一的场景
使用示例:
#include <memory>
using namespace std;
int main()
{
unique_ptr<int> ptr1(new int(10)); //1.管理单个int对象
auto ptr2 = make_unique<int>(20); //2.或用更安全的make_unique(C++14)
// unique_ptr<int> ptr3 = ptr1; //3.不允许拷贝(编译报错)
unique_ptr<int> ptr4 = move(ptr1); //4.允许移动(所有权转移)
}
// 离开作用域时,ptr2、ptr4自动释放内存
2. std::shared_ptr
std::shared_ptr
- 基本特性:共享所有权,通过引用计数记录有多少个 shared_ptr 管理同一块内存,当计数为 0 时自动释放
- 适用场景:需要多个指针共享同一资源,常用于需要拷贝指针的场景(如:容器中存储的对象被多个地方引用)
使用示例:
#include <memory>
#include <iostream>
using namespace std;
int main()
{
// 多个智能指针共享同一个引用计数
auto ptr1 = make_shared<int>(30);
cout << ptr1.use_count() << endl;
shared_ptr<int> ptr2 = ptr1; // 引用计数变为2
cout << ptr1.use_count() << endl;
//使用大括号定义了一个 “临时代码块” ---> 定义了一个局部作用域
{
shared_ptr<int> ptr3 = ptr1; // 引用计数变为3
cout << ptr1.use_count() << endl;
}
// ptr3销毁,引用计数变回2
cout << ptr1.use_count() << endl;
} // ptr1、ptr2销毁,引用计数变为0,内存释放

3. std::weak_ptr
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—> 解决循环引用
三、智能指针的具体使用
使用 C++ 智能指针的核心是利用其自动管理内存的特性,避免手动 new/delete 导致的问题。
以下是三种常用智能指针的具体使用方法和场景:
1. 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)

2. 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; // 引用计数 = 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
cout << "局部域中的计数:" << ptr1.use_count() << endl; // 输出:4
} // ptr4 销毁,计数 = 3
//6.手动释放(重置指针,计数减少)
ptr2.reset(); // ptr2 不再指向内存,计数 = 2
}
// ptr1、ptr3 销毁,计数 = 0 → 内存自动释放

3. 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的引用计数为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仍存在
//注意:访问weak_ptr指向的对象:必须通过lock()方法转换为shared_ptr
if (auto temp = node1->next.lock())
{
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,没有循环引用,所有内存正常释放(无内存泄漏)

四、为什么需要智能指针
在 C++ 中,智能指针的出现主要是为了:解决手动管理动态内存时容易出现的问题,其核心价值在于自动管理内存生命周期,避免内存相关的 bug。
具体来说,需要智能指针的原因可以从以下几个方面理解:
1. 避免内存泄漏
- 手动管理动态内存(使用
new分配、delete释放)时,若因逻辑疏漏导致delete未执行,会造成内存泄漏(已分配的内存无法回收,直到程序结束)
void func()
{
int* ptr1 = new int(10);
int* ptr2 = ptr; // 两个指针指向同一块内存
delete ptr1;
delete ptr2; // 重复释放,行为未定义
}
- 智能指针会在自身生命周期结束时(如:超出作用域、被销毁)自动调用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的共享所有权),简化了内存管理的逻辑,降低了人为出错的概率
5. 总结
总结:
C++ 没有垃圾回收机制,动态内存的分配与释放完全依赖程序员手动控制,这使得内存管理成为 C++ 开发中的常见痛点(内存泄漏、重复释放、异常安全等)
智能指针通过封装原始指针,在编译期和运行期自动处理内存的释放时机,本质上是 用对象管理资源:“资源获取即初始化”(RAII:Resource Acquisition Is Initialization)思想的体现,大幅提升了代码的安全性和可维护性
五、智能指针的本质与优势
1. 本质
智能指针(unique_ptr/shared_ptr)是 RAII思想 的典型实现,RAII 设计思想:用对象生命周期管理资源
- RAII 是 Resource Acquisition Is Initialization 的缩写,中文译为 “资源获取即初始化”
它是一种管理动态资源的设计思想,核心逻辑是:
- 资源获取:在对象构造时获取资源(如:内存、文件句柄、网络连接)
- 资源持有:资源在对象的生命周期内始终有效
- 资源释放:对象析构时自动释放资源(无需手动调用
delete/close等)
通过绑定 “资源的生命周期” 与 “对象的生命周期”,避免因手动管理失误(如:忘记释放、异常导致跳过释放)引发的资源泄漏。
2. 优势
智能指针的核心优势:
- 自动释放:无需手动调用
delete,即使程序因异常跳转,也能保证内存被释放 - 避免泄漏:解决了
“忘记释放”、“重复释放”、“释放后继续使用”等常见问题 - 清晰语义:通过
unique_ptr(独占)和shared_ptr(共享)清晰表达资源的管理方式
六、使用智能指针的注意事项
1. RAII 与资源释放
- 智能指针的析构函数默认调用 delete 释放资源
- 若管理的资源不是new分配的(如:文件句柄、网络连接),需自定义删除器
- 注意:避免用智能指针管理非动态内存,否则会导致 delete 栈内存的未定义行为
/*------------- 自定义删除器:释放文件句柄 -------------*/
void deleteFile(FILE* fp)
{
fclose(fp);
}
// 析构时调用 deleteFile 释放资源
std::shared_ptr<FILE> p( fopen("test.txt", "w"), 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));
七、智能指针的删除器
1. 什么是删除器?
删除器:是智能指针(如:unique_ptr、shared_ptr)用于释放所管理资源的一个重要机制。
- 它的核心作用是定义资源的释放方式,确保智能指针在生命周期结束时,能正确、安全地回收所管理的资源
- 它允许自定义资源释放的方式,而不仅限于 delete 操作,这使得智能指针可以管理各种类型的资源,如:文件句柄、网络连接、锁等
2. 为什么要引入删除器?
智能指针默认使用 delete 运算符释放资源,但实际场景中存在很多特殊情况:
- 动态数组:需要用
delete[]释放(而非delete) - 非内存资源(如:文件句柄
FILE*、网络套接字socket):需要调用特定函数释放(如:fclose、closesocket) - 自定义的资源释放逻辑(如:日志记录、状态清理等)
此时,默认的 delete 无法满足需求,必须通过自定义删除器来指定资源的释放方式。
总结:使用删除器可以管理非 new 分配的资源(如:malloc、fopen 分配的资源)
3. 删除器的本质是什么?
删除器本质是一个= “可调用对象”=,它可以是:
普通函数仿函数(重载 operator() 的类对象)lambda 表达式函数指针std::function包装的函数......
当智能指针需要释放其管理的资源时,会调用这个删除器而不是简单的 delete
八、删除器怎么使用?
1. 与 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、3
ArrayDeleter<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;
/*------------------------ 定义“仿函数”删除器 ------------------------*/
struct FileDeleter
{
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. labmda表达式作为删除器
- 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和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;
}

2. 与 shared_ptr 配合使用
shared_ptr 是共享所有权的智能指针,其模板参数不需要包含删除器类型(删除器类型不影响 shared_ptr 本身的类型),只需在构造时传入删除器即可,这使得 shared_ptr 对删除器的使用更灵活
1. 函数指针作为删除器
#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"; }
};
/*------------------------ 定义“函数指针”删除器 ------------------------*/
template<class T>
void ArrayDeleter(T* ptr) //注意:数组中元素的类型(此处为Resource)
{
if (ptr) //安全检查:避免对空指针执行释放操作
{
//1.显示使用自定义删除器释放数组的提示
cout << "用 ArrayDeleter 释放数组\n";
//2.用delete[]释放动态数组(与new[]配对)
delete[] ptr;
}
}
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. 仿函数作为删除器
/*------------------------ 定义一个资源类 ------------------------*/
struct Resource
{
int id; // 资源的唯一标识,用于区分不同的Resource对象
Resource(int i) : id(i) { cout << "Resource " << id << " 创建\n"; }
~Resource() { cout << "Resource " << id << " 被默认释放\n"; }
};
/*------------------------ 定义“函数指针”删除器 ------------------------*/
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;
}

3. 删除器使用的总结
#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;
}
4. unique_ptr与shared_ptr使用删除器的差异是什么?
总结:unique_ptr 与 shared_ptr 使用删除器的差异

5. 使用删除器的需要注意什么?
使用删除器的注意事项:
- 删除器的参数类型必须匹配:删除器接收的参数类型必须与智能指针管理的资源类型一致(如:管理
FILE*资源,删除器必须接收FILE*) - 避免删除空指针:删除器内部最好判断
ptr != nullptr,避免对空指针执行释放操作(虽然delete nullptr是安全的,但fclose(nullptr)可能崩溃) - shared_ptr的删除器存储在控制块:
shared_ptr的删除器与引用计数一起存在控制块中,因此即使删除器很大,也不会增加shared_ptr本身的大小 - 默认删除器的局限性:智能指针默认使用
delete释放资源,因此管理动态数组或非内存资源时,必须显式指定删除器,否则会导致未定义行为
总结:
删除器的使用核心:根据资源类型,定义对应的释放逻辑,然后将其绑定到智能指针。
具体选择哪种形式的删除器(函数指针/仿函数 /lambda),取决于场景的复杂度和复用需求。
结语
智能指针是 C++11 引入的最具价值的语言特性之一,它用 RAII 的思想彻底改变了传统的资源管理方式,让 C++ 在保持高性能的同时,也拥有了更强的安全性。
在本文中,我们从基础概念到实际使用,从所有权模型到删除器机制,全面分析了 unique_ptr、shared_ptr、weak_ptr 的应用场景及注意事项。希望你在阅读之后,不仅能正确使用智能指针,更能理解其设计背后的思想:用对象的生命周期管理资源,让资源管理自动化、安全化、工程化。
智能指针并不是银弹,但它们确实是现代 C++ 编程中不可或缺的可靠工具。
以上就是本文的所有内容了,如果觉得文章对你有帮助,欢迎 点赞⭐收藏 支持!如有疑问或建议,请在评论区留言交流,我们一起进步
分享到此结束啦一键三连,好运连连!你的每一次互动,都是对作者最大的鼓励!
征程尚未结束,让我们在广阔的世界里继续前行!🚀
更多推荐

所有评论(0)