【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

Spring AI

目录 基本概念 什么是 AI 模型(Model) 大语言模型  (LLM) 提示词 (Prompt) 词元(Token) Spring AI 是什么 快速入门 环境要求 申请 API Key 项目创建 接口编写 核心接口 ChatModel  ChatClient 消息类型 SystemMessage UserMessage AssistantMessage 输出格式 结构化输出 流式输出 SSE 协议介绍 SSE 数据格式 data event id retry SSE 使用示例 Flux Advisors 基本概念 什么是 AI AI:也就是 人工智能(

By Ne0inhk
使用 VS Code 连接 MySQL 数据库

使用 VS Code 连接 MySQL 数据库

文章目录 * 前言 * VS Code下载安装 * 如何在VS Code上连接MySQL数据库 * 1、打开扩展 * 2、安装MySQL插件 * 3、连接 * 导入和导出表结构和数据 前言 提示:这里可以添加本文要记录的大概内容: 听说VS Code不要钱,功能还和 Navicat 差不多,还能在上面打游戏 但是没安装插件是不行的 发现一个非常牛的博主 还有一个非常牛的大佬 提示:以下是本篇文章正文内容,下面案例可供参考 VS Code下载安装 VS Code下载安装 如何在VS Code上连接MySQL数据库 本篇分享是在已有VS Code这个软件的基础上,数据库举的例子是MySQL 1、打开扩展 2、安装MySQL插件 在搜索框搜索 MySQL和 MySQL Syntax,下载这三个插件 点击下面的插件,选择【install】安装

By Ne0inhk

Claude Code、OpenClaw、OpenCode 架构对比 — 及 SkillLite 的借鉴与取长补短

一、概述 当前 AI 编码 Agent 有三条主流路线:Claude Code(闭源商业)、OpenClaw(开源多通道网关)、OpenCode(开源编码 Agent)。SkillLite 在深度研究上述框架之后整合各个框架的长处,取长补短,构建:开源 + 本地 + 安全沙箱 + 引擎级自进化。本文从架构视角对比四者,并说明 SkillLite 如何借鉴三者之长、补三者之短。 维度 Claude Code OpenClaw OpenCode SkillLite-agent 定位 闭源商业 AI 编码助手 开源多通道 AI 网关 开源 AI 编码 Agent 开源安全自进化 Agent 引擎 技术栈 闭源(

By Ne0inhk
从语法兼容到语义一致:深度解析金仓如何“无感”承接MySQL复杂业务

从语法兼容到语义一致:深度解析金仓如何“无感”承接MySQL复杂业务

前言 现在国产化替代已经走到“深水区”了,数据库迁移早就不是简单把数据从A库搬到B库这么简单,而是要保证业务不停、系统稳当的深度重构。以前很多迁移项目只盯着“数据层”同步,压根没管“语义层”能不能对上,结果应用一上线就各种报错、性能忽高忽低,逼得开发团队大改代码——既费人又费时间,还藏着回归测试的大风险。 针对这个行业老大难问题,电科金仓搭了一套从内核解析到工具链的全栈兼容体系,让KingbaseES从只会“翻译”MySQL语法,升级到能“适配”语义逻辑。它不光能看懂MySQL的各种指令,还能自动修正复杂逻辑的差异,让老业务系统迁过来之后,不只是“能跑”,更是“跑得稳、跑得快”。今天咱们就掰开揉碎了讲,看看金仓是怎么做到MySQL迁移“无感”过渡的。 目录 * 前言 * 一、迁移的深水区:从“能跑”到“好用” * 二、语法兼容:不用改代码,直接“

By Ne0inhk