C++ | 智能指针

在 C++ 编程中,内存管理一直是一个重要且复杂的问题。手动管理内存容易引发如内存泄漏、悬空指针等一系列问题,不仅增加了程序的调试难度,还可能影响程序的稳定性和性能。为了解决这些问题,C++11 引入了智能指针(Smart Pointers)这一强大工具。本文将深入探讨 C++ 智能指针的原理、优势及其常见使用场景。

一、智能指针的原理

智能指针本质上是一个类模板,它利用了 RAII(Resource Acquisition Is Initialization,资源获取即初始化)技术对普通指针进行封装,使其行为类似指针,却又具备自动内存管理的能力。简单来说,智能指针在构造时获取资源(如分配内存),在析构时释放资源,从而确保资源在不再需要时能被正确释放。

C++ 标准库提供了三种主要的智能指针类型:std::unique_ptr、std::shared_ptr 和 std::weak_ptr ,它们各自有着不同的特性和适用场景。

1.1 std::unique_ptr

std::unique_ptr 代表着对动态分配对象的独占所有权,即同一时刻只能有一个 std::unique_ptr 指向一个对象。当 std::unique_ptr 离开其作用域或被显式重置时,它会自动删除关联的对象。这一过程通过禁止拷贝语义,仅提供移动语义来实现。例如:

#include <iostream>

#include <memory>

int main() {

std::unique_ptr<int> uniquePtr(new int(10));

std::cout << *uniquePtr << std::endl; // 输出10

// uniquePtr离开作用域,自动释放内存

return 0;

}

在这个例子中,uniquePtr 离开 main 函数作用域时,其所指向的内存会被自动释放,无需手动调用 delete 。

使用场景:

  • 函数内部临时对象管理:在函数内部创建一个动态分配的对象,且该对象只在函数内部使用,不需要在函数外部共享时,std::unique_ptr 是很好的选择。例如:

void processData() {

std::unique_ptr<int> data(new int(100));

// 处理data

// data离开作用域自动释放

}

  • 对象所有权转移:当需要将一个对象的所有权从一个函数转移到另一个函数时,std::unique_ptr 可以通过移动语义实现高效的转移。

std::unique_ptr<int> createObject() {

return std::unique_ptr<int>(new int(40));

}

void processObject(std::unique_ptr<int> obj) {

// 处理obj

}

int main() {

auto obj = createObject();

processObject(std::move(obj));

// obj离开作用域,自动释放内存

return 0;

}

1.2 std::shared_ptr

std::shared_ptr 实现了对动态分配对象的共享所有权,多个 std::shared_ptr 可以指向同一个对象。它使用引用计数(reference counting)来跟踪指向对象的引用数量。当一个 std::shared_ptr 被创建或拷贝时,引用计数增加;当一个 std::shared_ptr 被销毁或赋值给其他对象时,引用计数减少。当引用计数减为 0 时,对象被自动删除。例如:

#include <iostream>

#include <memory>

int main() {

std::shared_ptr<int> sharedPtr1(new int(20));

std::shared_ptr<int> sharedPtr2 = sharedPtr1; // 引用计数增加

std::cout << *sharedPtr1 << " " << *sharedPtr2 << std::endl; // 输出20 20

// sharedPtr1和sharedPtr2离开作用域,引用计数减为0,对象被删除

return 0;

}

引用计数增加的时机

  1. 初始化:当通过构造函数创建一个新的std::shared_ptr ,并指向一个新分配的对象时,引用计数初始化为 1 。例如std::shared_ptr<int> ptr(new int(5)); 。
  2. 拷贝构造:当使用一个已有的std::shared_ptr 来拷贝构造一个新的std::shared_ptr 时,引用计数增加。如std::shared_ptr<int> ptr2 = ptr; 。
  3. 赋值:当将一个std::shared_ptr 赋值给另一个std::shared_ptr ,且这两个std::shared_ptr 指向同一个对象时,引用计数增加。例如:

引用计数减少的时机

  1. 析构:当std::shared_ptr 离开其作用域被销毁时,引用计数减少。例如在上述代码中,ptr和temp离开作用域时,引用计数会分别减少。
  2. 赋值:当一个std::shared_ptr 被赋值为另一个指向不同对象的std::shared_ptr 时,原对象的引用计数减少,新对象的引用计数增加(如果新对象不是第一次被引用)。如ptr = std::shared_ptr<int>(new int(20)); ,此时原来指向的对象引用计数减 1 ,新对象引用计数初始化为 1 。
  3. reset:调用std::shared_ptr 的reset 成员函数时,引用计数减少。如果reset 时传入新的指针,那么新对象引用计数增加。例如ptr.reset(new int(30)); ,原对象引用计数减 1 ,新对象引用计数初始化为 1 。

使用场景:

  • 对象需要在多个地方共享:当一个对象需要在不同的函数、模块甚至不同的线程之间共享时,std::shared_ptr 非常适用。例如实现一个全局的配置对象,多个模块都需要访问和修改这个配置对象:

std::shared_ptr<Config> globalConfig(new Config());

void module1() {

auto config = globalConfig;

// 使用config

}

void module2() {

auto config = globalConfig;

// 使用config

}

  • 动态数组管理:当需要动态分配一个数组,并且希望在多个地方共享这个数组时,可以使用std::shared_ptr ,结合std::make_shared 来创建数组:

std::shared_ptr<int[]> sharedArray = std::make_shared<int[]>(10);

1.3 std::weak_ptr

std::weak_ptr 通常与 std::shared_ptr 一起使用,用于解决可能出现的循环引用问题,避免内存泄漏。std::weak_ptr 不参与引用计数,它提供了一个非拥有性、观察性的引用,指向由 std::shared_ptr 管理的对象。可以通过 lock() 方法尝试获取一个有效的 std::shared_ptr ,如果对象已被销毁,lock() 将返回一个空的 std::shared_ptr 。例如:

#include <iostream>

#include <memory>

int main() {

std::shared_ptr<int> sharedPtr(new int(30));

std::weak_ptr<int> weakPtr = sharedPtr;

if (auto temp = weakPtr.lock()) {

std::cout << *temp << std::endl; // 输出30

}

// sharedPtr离开作用域,对象被删除

// 此时weakPtr指向的对象已不存在

return 0;

}

使用场景:

  • 解决循环引用:在对象之间存在循环引用时,使用std::weak_ptr 可以打破循环,避免内存泄漏。例如在一个双向链表的实现中,节点之间相互引用,如果都使用std::shared_ptr 会导致循环引用,使用std::weak_ptr 可以解决这个问题:

class Node;

using SharedNode = std::shared_ptr<Node>;

using WeakNode = std::weak_ptr<Node>;

class Node {

public:

int data;

SharedNode next;

WeakNode prev;

Node(int value) : data(value) {}

};

  • 监控对象生命周期:当需要监控一个由std::shared_ptr 管理的对象是否还存在,但又不想增加其引用计数时,可以使用std::weak_ptr 。例如在一个缓存系统中,缓存对象由std::shared_ptr 管理,其他模块可以通过std::weak_ptr 来检查缓存对象是否还在缓存中:

std::shared_ptr<CacheObject> cacheObject(new CacheObject());

std::weak_ptr<CacheObject> weakCache = cacheObject;

// 其他地方检查缓存对象是否存在

if (auto temp = weakCache.lock()) {

// 缓存对象存在

} else {

// 缓存对象已被释放

}

二、智能指针的优势

2.1 自动内存管理

智能指针最显著的优势就是自动内存管理。它避免了手动调用 delete 操作符,降低了因忘记释放内存而导致内存泄漏的风险。无论是在正常程序流程中,还是在异常处理情况下,智能指针都能确保动态分配的内存被正确释放。

2.2 防止悬空指针

悬空指针(dangling pointer)是指指向已被释放内存的指针。使用智能指针时,当对象被销毁,指针会自动变为空指针(对于 std::unique_ptr 和 std::shared_ptr ),或者失效(对于 std::weak_ptr ),从而避免了悬空指针的出现,提高了程序的安全性。

2.3 简化代码

智能指针简化了内存管理的代码逻辑,使代码更加简洁和易读。开发人员无需再手动编写复杂的内存分配和释放代码,专注于实现程序的核心功能。

2.4 线程安全

std::shared_ptr 的引用计数操作是线程安全的,这使得在多线程环境中使用智能指针时,能够有效避免因多线程访问共享资源而导致的数据竞争问题,提高了多线程程序的稳定性和可靠性。

三、注意事项

尽管智能指针带来了诸多便利,但在使用时也需要注意一些问题。

3.1 性能开销

与普通指针相比,智能指针由于需要额外的内存来存储引用计数等信息,以及执行引用计数的增加和减少操作,会带来一定的性能开销。在对性能要求极高的场景下,需要谨慎评估智能指针的使用。

3.2 循环引用

虽然 std::weak_ptr 可以解决 std::shared_ptr 之间的循环引用问题,但如果不小心在代码中引入了循环引用,会导致对象无法被正确释放,造成内存泄漏。因此,在设计和编写代码时,需要特别注意避免循环引用的出现。

3.3 与传统指针的混用

在使用智能指针的代码中,应尽量避免与传统指针混用,以免引发难以调试的问题。如果必须使用传统指针,应确保对其进行正确的管理,避免出现内存泄漏和悬空指针等问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/972131.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

机器学习实战(1): 入门——什么是机器学习

机器学习入门——什么是机器学习&#xff1f; 欢迎来到“机器学习实战”系列的第一篇博文&#xff01;在这一集中&#xff0c;我们将带你了解机器学习的基本概念、主要类型以及它在现实生活中的应用。无论你是初学者还是有一定经验的开发者&#xff0c;这篇文章都会为你打下坚…

HTML【详解】input 标签

input 标签主要用于接收用户的输入&#xff0c;随 type 属性值的不同&#xff0c;变换其具体功能。 通用属性 属性属性值功能name字符串定义输入字段的名称&#xff0c;在表单提交时&#xff0c;服务器通过该名称来获取对应的值disabled布尔值禁用输入框&#xff0c;使其无法被…

《TSP6K数据集进行交通场景解析》学习笔记

paper&#xff1a;2303.02835 GitHub&#xff1a;PengtaoJiang/TSP6K: The official PyTorch code for "Traffic Scene Parsing through the TSP6K Dataset". 目录 摘要 1、介绍 2、相关工作 2.1 场景解析数据集 2.2 场景解析方法 2.3 实例分割方法 2.4 无监…

Tomcat下载,安装,配置终极版(2024)

Tomcat下载&#xff0c;安装&#xff0c;配置终极版&#xff08;2024&#xff09; 1. Tomcat下载和安装 进入Apache Tomcat官网&#xff0c;我们可以看到这样一个界面。 现在官网目前最新版是Tomcat11&#xff0c;我用的是Java17&#xff0c;在这里我们选择Tomcat10即可。Tom…

【算法】回溯算法

回溯算法 什么是回溯 人生无时不在选择。在选择的路口&#xff0c;你该如何抉择 ..... 回溯&#xff1a; 是一种选优搜索法&#xff0c;又称为试探法&#xff0c;按选优条件向前搜索&#xff0c;以达到目标。但当探索到某一步时&#xff0c;发现原先选择并不优或达不到目标&am…

图解JVM-1. JVM与Java体系结构

一、前言 在 Java 开发的广袤天地里&#xff0c;不少开发者都遭遇过令人头疼的状况。线上系统毫无征兆地卡死&#xff0c;陷入无法访问的僵局&#xff0c;甚至直接触发 OOM&#xff08;OutOfMemoryError&#xff0c;内存溢出错误&#xff09;&#xff1b;面对 JVM 的 GC&#…

深入浅出 Python Logging:从基础到进阶日志管理

在 Python 开发过程中&#xff0c;日志&#xff08;Logging&#xff09;是不可或缺的调试和监控工具。合理的日志管理不仅能帮助开发者快速定位问题&#xff0c;还能提供丰富的数据支持&#xff0c;让应用更具可观测性。本文将带你全面了解 Python logging 模块&#xff0c;涵盖…

设计模式15:中介者模式

系列总链接&#xff1a;《大话设计模式》学习记录_net 大话设计-CSDN博客 1.概述 中介者模式&#xff08;Mediator Pattern&#xff09;是一种行为设计模式&#xff0c;旨在通过一个中介对象来封装一系列对象之间的交互方式&#xff0c;从而减少这些对象间的直接依赖。在该模式…

爬取网站内容转为markdown 和 html(通常模式)

我们遇到一些自己喜欢内容&#xff0c;想保存下来&#xff0c;手动复制粘贴很麻烦&#xff0c;我们使用 python 来爬取这些内容。 一、代码 downlod.py import os import requests from bs4 import BeautifulSoup from urllib.parse import urljoin# 目标网页&#xff08;可…

【Linux】命令操作、打jar包、项目部署

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 一&#xff1a;Xshell下载 1&#xff1a;镜像设置 二&#xff1a;阿里云设置镜像Ubuntu 三&#xf…

Unity合批处理优化内存序列帧播放动画

Unity合批处理序列帧优化内存 介绍图片导入到Unity中的处理Unity中图片设置处理Unity中图片裁剪 创建序列帧动画总结 介绍 这里是针对Unity序列帧动画的优化内容&#xff0c;将多个图片合批处理然后为了降低Unity的内存占用&#xff0c;但是相对的质量也会稍微降低。可自行进行…

day4 多连联表慢查询sql查询优化

1.Explain分析sql语句出现的字段是什么意思 id: 查询的序列号&#xff0c;表示查询中 select 子句或操作表的顺序。 如果 id 相同&#xff0c;则执行顺序从上到下。 如果 id 不同&#xff0c;如果是子查询&#xff0c;id 的值会递增&#xff0c;id 值越大优先级越高&#xff0c…

基于豆瓣2025电影数据可视化分析系统的设计与实现

✔️本项目旨在通过对豆瓣电影数据进行综合分析与可视化展示&#xff0c;构建一个基于Python的大数据可视化系统。通过数据爬取收集、清洗、分析豆瓣电影数据&#xff0c;我们提供了一个全面的电影信息平台&#xff0c;为用户提供深入了解电影产业趋势、影片评价与演员表现的工…

力扣高频sql 50题(基础版) :NULL, 表连接,子查询,case when和avg的结合

NULL的处理 nvl(字段,num) 和数字进行比较需要先使用nvl(字段,num)函数处理空值 思路: 没有被id 2 的客户推荐>> 过滤条件 referee_id !2 没有被id 2 的客户推荐>>被其他客户推荐, 但是也有可能没有被任何客户推荐>>NULL 考点: NULL是 不一个具体的数…

夜莺监控发布 v8.beta5 版本,优化 UI,新增接口认证方式便于鉴权

以防读者不了解夜莺&#xff0c;开头先做个介绍&#xff1a; 夜莺监控&#xff0c;英文名字 Nightingale&#xff0c;是一款侧重告警的监控类开源项目。类似 Grafana 的数据源集成方式&#xff0c;夜莺也是对接多种既有的数据源&#xff0c;不过 Grafana 侧重在可视化&#xff…

Python - 爬虫利器 - BeautifulSoup4常用 API

文章目录 前言BeautifulSoup4 简介主要特点&#xff1a;安装方式: 常用 API1. 创建 BeautifulSoup 对象2. 查找标签find(): 返回匹配的第一个元素find_all(): 返回所有匹配的元素列表select_one() & select(): CSS 选择器 3. 访问标签内容text 属性: 获取标签内纯文本get_t…

认识 ADB(Android Debug Bridge,Android SDK 中的一个工具)

一、ADB 概述 ADB&#xff0c;全称 Android Debug Bridge&#xff0c;是 Android SDK 中的一个工具 ADB 位于 Android SDK 下 platform-tools 目录中 ADB 起到调试桥的作用&#xff0c;ADB 可以让开发者通过 USB 连接安卓设备&#xff0c;并在电脑上执行各种命令&#xff0c;…

模拟解决哈希表冲突

目录 解决哈希表冲突原理&#xff1a; 模拟解决哈希表冲突代码&#xff1a; 负载因子&#xff1a; 动态扩容&#xff1a; 总结&#xff1a; HashMap和HashSet的总结&#xff1a; 解决哈希表冲突原理&#xff1a; 黑色代表一个数组&#xff0c;当 出现哈希冲突时&#xff0…

FPGA简介|结构、组成和应用

Field Programmable Gate Arrays&#xff08;FPGA&#xff0c;现场可编程逻辑门阵列&#xff09;&#xff0c;是在PAL、GAL、CPLD等可编程器件的基础上进一步发展的产物&#xff0c; 是作为专用集成电路&#xff08;ASIC&#xff09;领域中的一种半定制电路而出现的&#xff0c…

【机器学习】超参数调优指南:交叉验证,网格搜索,混淆矩阵——基于鸢尾花与数字识别案例的深度解析

一、前言&#xff1a;为何要学交叉验证与网格搜索&#xff1f; 大家好&#xff01;在机器学习的道路上&#xff0c;我们经常面临一个难题&#xff1a;模型调参。比如在 KNN 算法中&#xff0c;选择多少个邻居&#xff08;n_neighbors&#xff09;直接影响预测效果。 • 蛮力猜…