【C++】智能指针:内存管理的利器

【C++】智能指针:内存管理的利器

本文是小编巩固自身而作,如有错误,欢迎指出!

目录

一、何为智能指针

(1)传统指针的缺陷

RAII:

智能指针:

二、智能指针的使用

(1)C++标准库的智能指针

1. std::unique_ptr(独占型智能指针)

2. std::shared_ptr(共享型智能指针)

3. std::weak_ptr(弱引用智能指针)

三、delete删除器

示例 :释放 malloc 分配的内存(替代 free)

示例 :释放数组(默认 unique_ptr  用 delete[],这里自定义)、


一、何为智能指针

(1)传统指针的缺陷

传统指针的问题

  • 内存泄漏(忘记delete)
  • 悬垂指针(delete后继续访问)
  • 异常安全问题(抛出异常后,后面的delete语句没办法执行)

因此,为了解决上述问题,就出现了RAII和智能指针的思路

RAII:

RAII(Resource Acquisition Is Initialization,资源获取即初始化)是C++ 特有的核心编程范式,由 C++ 之父 Bjarne Stroustrup 提出,核心思想是将资源的生命周期完全绑定到对象的生命周期—— 通过对象的构造函数获取资源,析构函数释放资源,利用 C++ 自动调用析构函数的特性,确保资源(内存、文件句柄、锁、网络连接等)被及时、安全释放,从根本上避免资源泄漏。

简而言之就是

获取资源与构造绑定

释放资源与析构绑定

智能指针:

而智能指针就是在继承了RAII的基础上,让这个对象同时拥有普通指针的功能

二、智能指针的使用

(1)C++标准库的智能指针

1. std::unique_ptr(独占型智能指针)

  • 核心特性:独占所有权,同一时间只能有一个 unique_ptr 指向同一块内存,不支持拷贝copy),仅支持移动(move)。
  • 适用场景:管理独占资源(如单个对象、数组),是最常用的智能指针

示例

#include <iostream> #include <memory> class Test { public: Test(int id) : id_(id) { std::cout << "Test " << id_ << " 构造\n"; } ~Test() { std::cout << "Test " << id_ << " 析构\n"; } void show() { std::cout << "Test id: " << id_ << "\n"; } private: int id_; }; int main() { // 1. 创建 unique_ptr 管理单个对象 std::unique_ptr<Test> ptr1(new Test(1)); ptr1->show(); // 访问对象成员 // 2. 推荐:使用 std::make_unique(C++14 引入,更安全,避免内存泄漏) auto ptr2 = std::make_unique<Test>(2); // 3. 移动语义(转移所有权) std::unique_ptr<Test> ptr3 = std::move(ptr2); // ptr2 变为空,ptr3 接管 if (!ptr2) std::cout << "ptr2 已空\n"; // 4. 管理数组(自动调用 delete[]) std::unique_ptr<Test[]> arr_ptr(new Test[2]{ Test(3), Test(4) }); arr_ptr[0].show(); // 5. 手动释放(一般不需要,超出作用域自动释放) ptr1.reset(); // 释放 ptr1 指向的内存,ptr1 变为空 return 0; // ptr3、arr_ptr 超出作用域,自动析构 }

2. std::shared_ptr(共享型智能指针)

  • 核心特性:共享所有权,多个 shared_ptr 可指向同一块内存,通过引用计数(refcount)管理:
    • 新增一个 shared_ptr 指向该内存,引用计数 +1;
    • 某个 shared_ptr 销毁 / 重置,引用计数 -1;
    • 引用计数为 0 时,自动释放内存。
  • 适用场景:需要多个指针共享同一资源(如容器存储指针、多模块共享对象)

示例

#include <iostream> #include <memory> int main() { // 1. 创建(推荐 std::make_shared,效率更高,减少内存分配次数) std::shared_ptr<Test> ptr1 = std::make_shared<Test>(1); std::cout << "引用计数:" << ptr1.use_count() << "\n"; // 输出 1 // 2. 共享所有权(引用计数 +1) std::shared_ptr<Test> ptr2 = ptr1; std::cout << "引用计数:" << ptr1.use_count() << "\n"; // 输出 2 // 3. 重置(引用计数 -1) ptr1.reset(); std::cout << "引用计数:" << ptr1.use_count() << "\n"; // 输出 0 // 4. 最后一个 shared_ptr 销毁,内存释放 return 0; }

注意:避免循环引用(如两个 shared_ptr 互相指向对方),会导致引用计数无法归 0,内存泄漏。此时需要配合 std::weak_ptr 解决

3. std::weak_ptr(弱引用智能指针)

  • 核心特性:弱引用,不拥有资源所有权,仅观察 shared_ptr 管理的资源;
    • 不增加引用计数;
    • 可通过 lock() 方法获取有效的 shared_ptr(若资源未释放),否则返回空 shared_ptr
  • 适用场景:解决 shared_ptr 的循环引用问题,或需要观察资源但不影响其生命周期的场景。

示例

#include <iostream> #include <memory> class B; // 前向声明 class A { public: std::weak_ptr<B> b_ptr; // 弱引用,避免循环引用 ~A() { std::cout << "A 析构\n"; } }; class B { public: std::weak_ptr<A> a_ptr; // 弱引用 ~B() { std::cout << "B 析构\n"; } }; int main() { auto a = std::make_shared<A>(); auto b = std::make_shared<B>(); a->b_ptr = b; b->a_ptr = a; // 无循环引用,a/b 销毁时引用计数归 0,析构函数正常调用 return 0; }

三、delete删除器

  • 智能指针析构时默认是进行delete释放资源,这也就意味着如果不是new出来的资源(如:malloc、new[]、fopen...),交给智能指针管理,析构时就会崩溃
  • 为了解决上面这个问题:智能指针支持用户提供一个删除器,在删除器中实现资源的释放。
    但是提供位置不同
    unique_ptr:作为模板参数提供(建议提供仿函数)
    shared_ptr:作为构造函数参数提供(仿函数、lambda、函数指针等都可以,建议lambda)
    删除器的本质是一个可调用对象。当提供了删除器,在智能指针析构的时候,就会调用这个删除器去释放资源。(这个删除器的调用在智能指针析构函数内部)

示例 :释放 malloc 分配的内存(替代 free

#include <iostream> #include <memory> #include <cstdlib> // malloc/free // 自定义删除器:释放 malloc 分配的内存 void free_deleter(int* ptr) { std::cout << "自定义删除器:调用 free 释放内存\n"; std::free(ptr); } int main() { // 用 malloc 分配内存,绑定自定义删除器 int* raw_ptr = (int*)std::malloc(sizeof(int)); *raw_ptr = 100; // shared_ptr 创建时指定删除器 std::shared_ptr<int> ptr(raw_ptr, free_deleter); std::cout << *ptr << "\n"; // 输出 100 // 超出作用域时,自动调用 free_deleter,而非默认的 delete return 0; }

示例 :释放数组(默认 unique_ptr<T[]> 用 delete[],这里自定义)、

#include <iostream> #include <memory> // 自定义数组删除器 struct ArrayDeleter { template <typename T> void operator()(T* ptr) const { std::cout << "自定义删除器:释放数组\n"; delete[] ptr; } }; int main() { // 方式 1:显式指定删除器类型 std::unique_ptr<int, ArrayDeleter> ptr1(new int[5]{1,2,3,4,5}); // 方式 2:用 lambda(需要推导类型,C++14 及以上) auto lambda_deleter = [](int* ptr) { std::cout << "lambda 删除器:释放数组\n"; delete[] ptr; }; std::unique_ptr<int, decltype(lambda_deleter)> ptr2(new int[3]{6,7,8}, lambda_deleter); return 0; }

本次分享就到这里结束了,至此,c++的学习也就告一段落,后续会继续更新linux学习内容

Read more

Java ForkJoin 框架全面解析:分而治之的并行编程艺术

Java ForkJoin 框架全面解析:分而治之的并行编程艺术

文章目录 * 课程导言 * 适用对象 * 学习目标 * 为什么需要ForkJoin? * 第一部分:核心思想——分治法 + 工作窃取 * 1.1 分治法:从大化小,逐个击破 * 1.2 工作窃取:自动负载均衡的灵魂 * 为什么需要工作窃取? * 工作窃取的实现原理 * 第二部分:ForkJoin框架核心组件 * 2.1 ForkJoinPool —— 任务调度器 * 创建ForkJoinPool * 核心方法 * 2.2 ForkJoinTask —— 任务的抽象 * RecursiveTask<V> —— 有返回值的任务 * RecursiveAction —— 无返回值的任务 * fork() 与 join() 的奥秘 * 2.3 ForkJoinWorkerThread —— 执行任务的工作线程 * 第三部分:实战案例——从入门到精通

JAVA IO流进阶:字符流与字节流的深度应用

JAVA IO流进阶:字符流与字节流的深度应用

JAVA IO流进阶:字符流与字节流的深度应用 1.1 本章学习目标与重点 💡 掌握字节流与字符流的核心区别,能够根据实际开发场景选择合适的IO流实现文件操作。 💡 熟练运用缓冲流提升IO操作效率,解决大文件读写的性能问题。 💡 理解转换流的作用,处理不同编码格式的文件读写,避免乱码问题。 ⚠️ 本章重点是流的嵌套使用和资源释放的标准写法,这是实际开发中高频考点和易错点。 1.2 字节流与字符流的核心差异(七千字以上内容展开) 1.2.1 基本概念与设计初衷 💡 字节流以byte为基本单位进行数据传输,它可以处理所有类型的文件,比如图片、视频、音频、文本等。 字符流以char为基本单位进行数据传输,它专门用于处理文本文件,底层会涉及字符编码的转换。 字节流的核心类是InputStream和OutputStream,字符流的核心类是Reader和Writer。 两者都是抽象类,实际开发中我们使用的是它们的子类,比如FileInputStream、FileWriter等。 ✅ 核心结论:处理非文本文件用字节流,处理文本文件优先用字符流。 1.2.2 代码实操:字

AI动画剧本、脚本、分镜头生成提示词

豆包模型生成AI动画短片提示词-仅作参考 提示词1:剧本创作及脚本分镜头创作 发给豆包:【假设你是一位有30年电影拍摄的世界顶级导演,拥有丰富的电影拍摄经验和超高的电影拍摄技术,同时也擅长各种影片的剧本创作。 我需要你为我创作一部时长约30s的,带有美术设计、音效设计、有足够看点的剧本,同时写出故事梗概、角色设计。 故事的主题是月圆之夜的森林,九尾狐发现灵草并吃下,幻化成美人,惊讶(用户自由发挥) 一共设计6个分镜头,每个分镜头约5s,每个镜头有长有短,画面内容要描述的足够详细,脚本中要描述画面细节、角色配置、环境声音、音效、台词和旁白等,出现主角画面时要描述主角的具体特征 尽情释放你的潜能,创作出一部举世瞩目的佳作,切记,千万别落入俗套! 生成结果会用于AI视频生成,因此在内容描述部分,尽量以AI视频软件能理解的方式进行书写,需要古风写实风格。 不要表格,直接以回复的形式给出。】 脚本分镜头文字转图片提示词: 发给豆包:【根据你生成的脚本,重新生成相应的图片,比例16:9,主体造型保持一致,比例 「16:9」,图片风格为「CG动画」