C++ 中 `shared_ptr` 的用法及常见陷阱解析

news2025/3/9 10:49:38

shared_ptr 是 C++ 标准库(<memory> 头文件)中提供的一种智能指针,用于管理动态分配的内存。它通过引用计数(reference counting)来实现多个 shared_ptr 对象共享同一块内存的所有权。当最后一个 shared_ptr 被销毁时,内存会被自动释放。


一、创建 shared_ptr 的几种方法

1. 使用 std::make_shared(推荐)

#include <memory>
#include <iostream>

int main() {
    std::shared_ptr<int> sp = std::make_shared<int>(42);
    std::cout << *sp << std::endl; // 输出 42
    return 0;
}
  • 优点:效率高,一次性分配对象和控制块的内存。
  • 适用场景:大多数情况下推荐使用。

2. 通过构造函数和原始指针

std::shared_ptr<int> sp(new int(42));
  • 特点:显式地将原始指针交给 shared_ptr 管理。
  • 注意:避免用同一原始指针初始化多个 shared_ptr,否则会导致双重释放(详见后文)。

3. 通过拷贝或赋值

std::shared_ptr<int> sp1 = std::make_shared<int>(42);
std::shared_ptr<int> sp2 = sp1; // 共享所有权,引用计数增 1
  • 特点:多个 shared_ptr 共享同一块内存,引用计数自动管理。

二、shared_ptr 的常见操作

1. 基本方法

  • 获取引用计数use_count() 返回当前共享该资源的 shared_ptr 数量。
    std::shared_ptr<int> sp1 = std::make_shared<int>(42);
    std::shared_ptr<int> sp2 = sp1;
    std::cout << sp1.use_count() << std::endl; // 输出 2
    
  • 检查是否为空if (!sp)sp.get() == nullptr
  • 重置reset() 清空或重新绑定。
    sp2.reset();            // 做了两步:将指向对象的指针设置为nullptr,引用计数减一
    sp2.reset(new int(40)); // 将指针指向新对象
    

2. 引用方法

  • 解引用:使用 * 访问对象。
    std::cout << *sp1 << std::endl; // 输出 42
    
  • 成员访问:对于类对象,使用 ->
    struct MyClass { int value = 10; };
    std::shared_ptr<MyClass> sp = std::make_shared<MyClass>();
    std::cout << sp->value << std::endl; // 输出 10
    
  • 获取原始指针get() 返回底层指针(但不建议直接操作)。

三、常见应用场景

  1. 动态资源管理

    • 替代手动 newdelete,确保内存自动释放。
      std::shared_ptr<int> sp = std::make_shared<int>(42);
      // 离开作用域时自动释放
      
  2. 函数参数传递

    • 避免拷贝开销,同时确保资源安全管理。
      void process(std::shared_ptr<int> ptr) {
          std::cout << *ptr << std::endl;
      }
      
  3. 工厂函数

    • 返回动态分配的对象时,使用 shared_ptr 传递所有权。
      std::shared_ptr<int> create_resource() {
          return std::make_shared<int>(100);
      }
      
  4. 管理第三方库资源

    • 使用自定义删除器管理非标准内存(如文件句柄、第三方库指针)。
      std::shared_ptr<FILE> file(
          fopen("test.txt", "r"),
          [](FILE* f) { if (f) fclose(f); }
      );
      

四、使用注意事项

1. 双重释放问题 (Double Deletion)

问题描述

当用同一原始指针初始化多个独立的 shared_ptr 时,每个 shared_ptr 析构时都会尝试释放内存,导致未定义行为。(每个shared_ptr是独立的,引用计数器也是独立的,引用计数为0时,都尝试释放对象,从而导致双重释放的问题)

示例:
int* raw = new int(42);
std::shared_ptr<int> sp1(raw);
std::shared_ptr<int> sp2(raw); // 错误!双重释放
解决方法:

使用 make_shared 或通过拷贝共享:

std::shared_ptr<int> sp1 = std::make_shared<int>(42);
std::shared_ptr<int> sp2 = sp1; // 正确

2. 循环引用问题 (Circular Reference)

问题描述

当对象间通过 shared_ptr 互相引用时,引用计数无法降到 0,导致内存泄漏。(每个节点的引用计数都是2,超出作用域时,调用析构函数,引用计数减一,但无法变为0,导致内存无法释放)

示例
class Node {
public:
    std::shared_ptr<Node> next;
    ~Node() { std::cout << "Destructor" << std::endl; }
};

int main() {
    std::shared_ptr<Node> node1 = std::make_shared<Node>();
    std::shared_ptr<Node> node2 = std::make_shared<Node>();
    node1->next = node2;
    node2->next = node1; // 循环引用
    return 0; // 内存泄漏
}
解决方法
  • 使用 std::weak_ptr 打破循环(引用计数都是1,超出作用域后,调用析构函数,引用计数变为0,释放内存)
    class Node {
    public:
        std::weak_ptr<Node> next; // 改为 weak_ptr
        ~Node() { std::cout << "Destructor" << std::endl; }
    };
    

3. 避免与原始指针混用

不要在 shared_ptr 之外直接操作原始指针(如通过 get() 获取后手动 delete),会导致未定义行为。

五、结语

shared_ptr 是 C++ 中管理动态内存的利器,通过引用计数实现资源共享和自动释放。掌握其创建方法和常见操作,能显著提升代码的安全性与可维护性。然而双重释放、循环引用等问题需要特别注意。通过合理使用 make_sharedweak_ptr ,可以有效规避这些陷阱。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android+SpringBoot的老年人健康饮食小程序平台

感兴趣的可以先收藏起来&#xff0c;还有大家在毕设选题&#xff0c;项目以及论文编写等相关问题都可以给我留言咨询&#xff0c;我会一一回复&#xff0c;希望帮助更多的人。 系统介绍 我将从经济、生活节奏、技术融合等方面入手&#xff0c;详细阐述居家养老管理模式兴起的…

ORB-SLAM2源码学习(六):相机跟踪(局部地图跟踪和关键帧创建)

目录 1.局部地图跟踪 1.1 更新局部关键帧UpdateLocalKeyFrames 1.2 更新局部地图点&#xff08;来自局部关键帧&#xff09;UpdateLocalPoints() 1.3 投影匹配 2. 对比四种跟踪方式以及使用的投影匹配 3.关键帧创建 3.1 判断是否需要创建新关键帧: NeedNewKeyFrame() 3…

【极客时间】浏览器工作原理与实践-2 宏观视角下的浏览器 (6讲) - 2.6 渲染流程(下):HTML、CSS和JavaScript,是如何变成页面的?

https://time.geekbang.org/column/article/118826 2.6 渲染流程&#xff08;下&#xff09;&#xff1a;HTML、CSS和JavaScript&#xff0c;是如何变成页面的&#xff1f; 2.5介绍了渲染流水线中的 DOM 生成、样式计算和布局三个阶段&#xff0c;2.6讲解渲染流水线后面的阶段…

DeepSeek开源Day4:DualPipeEPLB技术详解

2 月 24 日&#xff0c;DeepSeek 启动 “开源周”&#xff0c;第四个开源的代码库为 DualPipe 与 EPLB&#xff08;一下发布了两个&#xff09;。DualPipe 与 EPLB 依然使用了大量与 Hopper 架构绑定的技术。 DualPipe 是由 DeepSeek-AI 团队开发的一种双向流水线并行通信算法&…

阿里推出全新推理模型(因果语言模型),仅1/20参数媲美DeepSeek R1

阿里Qwen 团队正式发布了他们最新的研究成果——QwQ-32B大语言模型&#xff01;这款模型不仅名字萌萌哒(QwQ)&#xff0c;实力更是不容小觑&#xff01;&#x1f60e; QwQ-32B 已在 Hugging Face 和 ModelScope 开源&#xff0c;采用了 Apache 2.0 开源协议。大家可通过 Qwen C…

vue实现一个pdf在线预览,pdf选择文本并提取复制文字触发弹窗效果

[TOC] 一、文件预览 1、安装依赖包 这里安装了disjs-dist2.16版本&#xff0c;安装过程中报错缺少worker-loader npm i pdfjs-dist2.16.105 worker-loader3.0.8 2、模板部分 <template><div id"pdf-view"><canvas v-for"page in pdfPages&qu…

时间复杂度分析与递归,以新南UNSW的COMP2521作业题为例

作者&#xff1a;Smooth&#xff08;连接教育高级讲师&#xff09; 首发于&#xff1a;⁠⁠⁠⁠⁠⁠⁠UNSW学习知识库&#xff08;UNSW Study Wiki&#xff09; 创作时间&#xff1a;2025年3月5日 如何测度算法的时间性能&#xff1f;理论分析Theoretical Analysis 测度算法时…

基于CSDN资源,搭建AI赋能农业典型场景落地方案

农业场景&#xff0c;不但是信息化、自动化等薄弱的产业&#xff0c;更是AI落地困难的场景。基于此&#xff0c;想通过这篇文章查找一个CSDN相关资源&#xff0c;论证一下AI赋能农业三个典型场景的实现思路。 场景1&#xff1a;水质-土壤智能调控 **痛点&#xff1a;**水质恶…

python量化交易——金融数据管理最佳实践——使用qteasy大批量自动拉取金融数据

文章目录 使用数据获取渠道自动填充数据QTEASY数据拉取功能数据拉取接口refill_data_source()数据拉取API的功能特性多渠道拉取数据实现下载流量控制实现错误重试日志记录其他功能 qteasy是一个功能全面且易用的量化交易策略框架&#xff0c; Github地址在这里。使用它&#x…

RoboBrain:从抽象到具体的机器人操作统一大脑模型

25年2月来自北大、北京智源、中科院自动化所等的论文“RoboBrain: A Unified Brain Model for Robotic Manipulation from Abstract to Concrete”。 目前的多模态大语言模型&#xff08;MLLM&#xff09; 缺少三项必备的机器人大脑能力&#xff1a;规划能力&#xff0c;将复杂…

DeepSeek本地接口调用(Ollama)

前言 上篇博文&#xff0c;我们通过Ollama搭建了本地的DeepSeek模型&#xff0c;本文主要是方便开发人员&#xff0c;如何通过代码或工具&#xff0c;通过API接口调用本地deepSeek模型 前文&#xff1a;DeepSeek-R1本地搭建_deepseek 本地部署-CSDN博客 注&#xff1a;本文不仅…

SQL_语法

1 数据库 1.1 新增 create database [if not exists] 数据库名; 1.2 删除 drop database [if exists] 数据库名; 1.3 查询 (1) 查看所有数据库 show databases; (2) 查看当前数据库下的所有表 show tables; 2 数据表 2.1 新增 (1) 创建表 create table [if not exists…

全面回顾复习——C++语法篇1(基于牛客网C++题库)

注&#xff1a;牛客网允许使用万能头文件#include<bits/stdc.h> 1、求类型长度——sizeof&#xff08;&#xff09;函数 2、将浮点数四舍五入——round&#xff08;&#xff09;函数——前面如果加上static_cast会更安全一些 在C语言中可以使用printf&#xff08;“.0l…

一、数据库 MySQL 基础学习 (上)

一、数据库的概念 DB 数据库&#xff08;database&#xff09;&#xff1a;存储数据的“仓库”&#xff0c;保存一系列有组织的数据 DBMS&#xff1a;数据库管理系统(Database Management System)。数据库是通过 DBMS 创建和操作的容器 创建的 DBMS&#xff1a; MySQL、Oracl…

基于Django创建一个WEB后端框架(DjangoRestFramework+MySQL)流程

一、Django项目初始化 1.创建Django项目 Django-admin startproject 项目名 2.安装 djangorestframework pip install djangorestframework 解释: Django REST Framework (DRF) 是基于 Django 框架的一个强大的 Web API 框架&#xff0c;提供了多种工具和库来构建 RESTf…

AutoGen学习笔记系列(七)Tutorial - Managing State

这篇文章瞄准的是AutoGen框架官方教程中的 Tutorial 章节中的 Managing State 小节&#xff0c;主要介绍了如何对Team内的状态管理&#xff0c;特别是如何 保存 与 加载 状态&#xff0c;这对于Agent系统而言非常重要。 官网链接&#xff1a;https://microsoft.github.io/auto…

Redis渐进式遍历数据库

目录 渐进式遍历 数据库 渐进式遍历 keys*可以一次性的把整个redis中所有key都获取到&#xff0c;这个操作是非常危险的&#xff0c;因为可能一下获取到太多的key&#xff0c;阻塞redis服务器。要想很好的获取到所有的key&#xff0c;又不想出现卡死的情况&#xff0c;就可以…

基于单片机的速度里程表设计(论文+源码)

1 系统方案 本次智能速度里程表的总体架构如图2-1所示&#xff0c;在硬件上包括了STC89C52单片机&#xff0c;电机&#xff0c;显示模块&#xff0c;报警模块&#xff0c;DS1302时钟模块&#xff0c;超速检测模块&#xff0c;按键等等。在软件设计功能的功能上&#xff0c;按下…

计算机毕业设计Python+Django+Vue3微博数据舆情分析平台 微博用户画像系统 微博舆情可视化(源码+ 文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

Dify+DeepSeek | Excel数据一键可视化(创建步骤案例)(echarts助手.yml)(文档表格转图表、根据表格绘制图表、Excel绘制图表)

Dify部署参考&#xff1a;Dify Rag部署并集成在线Deepseek教程&#xff08;Windows、部署Rag、安装Ragan安装、安装Dify安装、安装ollama安装&#xff09; DifyDeepSeek - Excel数据一键可视化&#xff08;创建步骤案例&#xff09;-DSL工程文件&#xff08;可直接导入&#x…