C++智能指针shared_ptr详解
智能指针shared_ptr详解
- 一、简介
- 二、底层原理
- 2.1、引用计数
- 2.2、shared_ptr的构造和析构
- 2.3、shared_ptr的共享和拷贝
- 2.4、循环引用问题
- 三、shared_ptr的使用
- 3.1、创建一个shared_ptr
- 3.2、共享一个shared_ptr
- 3.3、使用删除器
- 3.4、解除关联
- 四、使用示例
- 总结
一、简介
C++智能指针shared_ptr是一种可以自动管理内存的智能指针,它是C++11新增的特性之一。与传统指针不同,shared_ptr可以自动释放所管理的动态分配对象的内存,并避免了手动释放内存的繁琐操作,从而减少了内存泄漏和野指针的出现。
shared_ptr是一个模板类,通过引用计数器实现多个智能指针共享对一个对象的所有权。每次复制一个shared_ptr对象时,该对象的引用计数器会增加1,当最后一个shared_ptr对象被销毁时,引用计数器减1,如果引用计数器变为0,则释放所管理的对象的内存。
使用shared_ptr需要包含头文件,并且可以通过以下方式创建:
std::shared_ptr<int> p(new int(10));
上面的代码创建了一个shared_ptr对象p,它指向一个动态分配的int类型对象,初始值为10。
在使用shared_ptr时,需要注意以下几点:
-
不要使用裸指针来初始化shared_ptr,否则可能导致多次删除同一个对象的情况。
-
避免在shared_ptr中存储数组,因为shared_ptr只能处理单个对象的释放,而不能正确地处理数组的销毁。
-
可以通过自定义删除器(deleter)来实现对对象的特定方式的释放。
-
shared_ptr可以作为函数参数传递,但要注意避免循环引用的问题,否则会导致内存泄漏。
shared_ptr是一种方便且安全的内存管理工具,能够有效地避免内存泄漏和野指针的出现。
二、底层原理

2.1、引用计数
shared_ptr的核心是引用计数技术。在每个shared_ptr对象中,都有一个指向所管理对象的指针和一个整型计数器。这个计数器统计有多少个shared_ptr对象指向该所管理对象。当一个新的shared_ptr对象指向同一块内存时,该内存的引用计数就会增加1。当一个shared_ptr对象不再指向该内存时,该内存的引用计数就会减少1。当引用计数为0时,说明没有任何shared_ptr对象指向该内存,此时该内存将会被自动释放。
2.2、shared_ptr的构造和析构
-
shared_ptr的构造函数需要一个指针作为参数,该指针指向要被管理的对象。当一个新的shared_ptr对象被创建时,它会尝试增加所管理对象的引用计数。如果该对象还未被其他shared_ptr对象管理,则会创建一个新的引用计数,并将其设置为1。否则,它会与已经存在的shared_ptr对象共享同一个引用计数。
-
shared_ptr的析构函数会尝试减少所管理对象的引用计数。如果引用计数变成0,则会自动释放所管理对象的内存。
-
shared_ptr的控制块(包含引用计数和删除器等信息)会在最后一个指向所管理对象的shared_ptr析构时被释放。当引用计数减为0时,就说明没有任何shared_ptr对象指向该所管理对象了,此时shared_ptr会自动调用删除器,并释放掉控制块。由于shared_ptr可以共享同一个控制块,因此只有所有shared_ptr对象都析构后,控制块才能被释放。如果一个shared_ptr对象使用reset()方法手动解除与所管理对象的关联,也会相应地减少引用计数,当引用计数变成0时,控制块也会被释放。
2.3、shared_ptr的共享和拷贝
shared_ptr可以与其他shared_ptr对象共享同一个指向对象的指针。当一个shared_ptr对象被复制时,它所管理的对象的引用计数也会增加1。因此,任何一个持有相同指针的shared_ptr对象都可以通过更改其所管理对象的状态来影响所有其他shared_ptr对象。
2.4、循环引用问题
shared_ptr在处理循环引用问题时非常有效。例如,如果一个对象A包含指向另一个对象B的shared_ptr,而对象B也包含指向对象A的shared_ptr,则这两个对象将形成循环引用。在这种情况下,如果使用普通指针,可能会出现内存泄漏。但是,由于shared_ptr使用引用计数技术,当没有任何其他shared_ptr对象引用这两个对象时,它们将被正确地释放。
三、shared_ptr的使用
创建shared_ptr对象的语法有以下几种方式:
- 通过new关键字创建
std::shared_ptr<int> p(new int);
- 通过make_shared函数创建,该函数可以避免使用new关键字
std::shared_ptr<int> p = std::make_shared<int>();
- 传递指针和删除器作为参数创建
void my_deleter(int* p) {delete p;
}std::shared_ptr<int> p(new int, my_deleter);
- 传递指针、删除器和分配器作为参数创建
void my_deleter(int* p) {delete p;
}struct MyAllocator {void* allocate(size_t size);void deallocate(void* ptr, size_t size);
};MyAllocator my_allocator;std::shared_ptr<int> p(new int, my_deleter, my_allocator);
- 从另一个
shared_ptr对象创建
std::shared_ptr<int> p1(new int);
std::shared_ptr<int> p2(p1);
- 使用移动语义从另一个
shared_ptr对象创建
std::shared_ptr<int> p1(new int);
std::shared_ptr<int> p2(std::move(p1));
3.1、创建一个shared_ptr
使用shared_ptr创建一个智能指针非常简单,只需要将一个指向动态分配内存的裸指针作为参数传递给shared_ptr的构造函数即可:
// 创建一个int类型的智能指针
std::shared_ptr<int> p(new int(10));
3.2、共享一个shared_ptr
shared_ptr可以与其他shared_ptr对象共享同一个指向对象的指针,这样就可以避免多次动态分配内存和释放内存的问题。共享一个shared_ptr可以通过复制构造函数和赋值运算符实现:
// 复制构造函数
std::shared_ptr<int> p1(new int(10));
std::shared_ptr<int> p2(p1);// 赋值运算符
std::shared_ptr<int> p3(new int(10));
std::shared_ptr<int> p4;
p4 = p3;
注意:共享一个shared_ptr会增加所管理对象的引用计数。因此,任何一个持有相同指针的shared_ptr对象都可以通过更改其所管理对象的状态来影响所有其他shared_ptr对象。
3.3、使用删除器
除了管理所分配的内存外,shared_ptr还可以使用删除器(deleter)来管理对象。删除器是一个函数或者函数对象,用于在shared_ptr释放所管理对象时执行特定的操作。删除器可以通过shared_ptr的模板参数指定:
// 使用Lambda表达式作为删除器
std::shared_ptr<int> p(new int(10), [](int* p){ delete[] p; });
3.4、解除关联
如果需要解除shared_ptr与所管理对象的关联,可以使用reset()方法:
std::shared_ptr<int> p(new int(10));
p.reset();
注意:当调用reset()方法后,所管理对象的引用计数会减少1。如果引用计数变成0,则会自动释放所管理对象的内存。
四、使用示例
#include <memory>
#include <iostream>using namespace std;class MyClass {
public:MyClass() { cout << "MyClass constructor" << endl; }~MyClass() { cout << "MyClass destructor" << endl; }void printInfo() { cout << "This is MyClass" << endl; }
};int main() {shared_ptr<MyClass> p1(new MyClass()); // 创建一个shared_ptr指向MyClass对象shared_ptr<MyClass> p2 = p1; // p1和p2都指向同一个MyClass对象p1->printInfo(); // 访问MyClass对象的成员函数p2.reset(); // 释放p2所指向的MyClass对象p1->printInfo(); // 由于p1仍然指向MyClass对象,所以此处输出"This is MyClass"return 0;
}
上述代码中,通过调用shared_ptr<MyClass>构造函数创建了两个指针p1和p2,并且它们都指向一个MyClass对象。我们调用reset()函数来释放p2所指向的MyClass对象,但是由于p1仍然指向该对象,所以在调用p1->printInfo()时仍然输出"This is MyClass"。当程序结束时,p1所指向的MyClass对象会被自动释放。
可以看到,使用shared_ptr可以很方便地避免内存泄漏和悬空指针等问题。另外,需要注意的是,shared_ptr指针之间的赋值和拷贝操作都会增加指向对象的引用计数,即使一个指针已经释放了它所指向的对象,只要其他指针还在使用该对象,该对象就不会被自动删除。因此,在使用shared_ptr时需要注意对象的生命周期,避免产生意外的副作用。
总结
智能指针是C++中一种重要的语言机制,其中shared_ptr是最常用和最经典的智能指针之一。
-
shared_ptr是一种引用计数的智能指针,可以共享同一个对象。
-
使用shared_ptr时,需要包含头文件< memory >。
-
创建shared_ptr对象时,可以直接将原始指针作为参数传递给构造函数,也可以使用make_shared函数进行创建。
-
对象的引用计数会在shared_ptr对象初始化、复制、释放时自动更新。
-
当某个shared_ptr对象被销毁时,它所指向的对象的引用计数会减少,如果引用计数为0,则该对象会被自动删除。
-
通过get函数可以获取shared_ptr对象所管理的原始指针。
-
通过reset函数可以重新绑定shared_ptr对象所管理的原始指针。
-
可以使用unique函数判断shared_ptr对象是否唯一拥有原始指针。
-
通常情况下,shared_ptr对象应该在栈上创建,而不是使用new运算符在堆上创建。
-
在多线程环境下使用shared_ptr时需要注意,需要采取线程安全措施,比如使用锁来保证引用计数的正确性。
-
shared_ptr是C++11中STL的一部分,它是一个模板类,用于管理动态地分配对象的内存。shared_ptr可以自动完成内存管理,确保内存被正确释放,并且非常易于使用。
-
shared_ptr是一个强大的智能指针类,它利用引用计数技术来管理动态分配的对象的内存。shared_ptr可以避免循环引用和内存泄漏等问题,并且易于使用,是C++程序员必不可少的工具之一。

相关文章:
C++智能指针shared_ptr详解
智能指针shared_ptr详解 一、简介二、底层原理2.1、引用计数2.2、shared_ptr的构造和析构2.3、shared_ptr的共享和拷贝2.4、循环引用问题 三、shared_ptr的使用3.1、创建一个shared_ptr3.2、共享一个shared_ptr3.3、使用删除器3.4、解除关联 四、使用示例总结 一、简介 C智能指…...
家政服务APP小程序开发功能详解
随着人们生活水平的提高,对家政服务的要求也越来越高。而传统的到家政公司寻找服务人员的方法显然已经无法满足人们需求,取而代之的是线上预约家政服务。家政服务App小程序软件可以满足用户在线预约,还可以根据自己的需求定制家政服务、选择家…...
【C++】deque的实现原理简单介绍
前言 deque被称为双端队列,它的出现主要是为了结合vector和list的优点并减小它们的缺点,实际上deque确实结合了vector和list的优点减小了它们的缺点,但是它的结合也让它自己的优点没有原始的vector和list那么极致,导致deque变得很…...
UWB隧道人员定位技术应用,施工作业安全精准保障
隧道施工的安全不仅关系到工程项目的质量和施工效率,也关系到我国的资金安全、施工人员和人民的生命财产安全。如何有效加强隧道施工的安全管理能力,成为隧道施工企业管理者最关心的问题。国家铁道局在《关于加强铁路隧道工程安全工作的若干意见》中指出…...
15.2 矩阵链乘法
1.代码 public class MatrixChainMultiplication {public static void main(String[] args) { // 在该代码中,我们首先创建了两个n * n的矩阵m和s,分别用于记录最优值和分割点。 其中m 矩阵 通过i j 来显示在i到j的矩阵链中最优解 // // …...
向隐形冠军学习:聚焦人效,用时间管理提效益
注: 本文来源于盖雅工场联合创始人兼CEO 章新波 在2023狮山论坛“ 向隐形冠军学习: 聚焦人效,用时间管理提效益 ”的主题分享。 文|章新波 整理 |盖雅学苑 在人力资源行业以及各大企业,「人效」这个词…...
Protocol Buffers Go Generated Code Guide
Protocol Buffers Go 代码生成指南 本主题准确描述了协议缓冲区编译器为任何给定的协议定义生成的Go代码。 编译器调用 协议缓冲区编译器需要一个插件来生成Go 代码。使用Go 1.16或更高版本安装,方法是运行: go install google.golang.org/protobuf/…...
Python VTK STL 映射三维模型表面距离
目录 前言: 效果: 实现步骤: Code: 前言: 本文介绍了Python VTK映射三维模型表面距离,通过如何使用VTK计算两个三维模型(stl)的表面距离,并将其距离值以颜色映射到模型,可用于对比 两相模型…...
C# 异常处理机制和常见的异常类型
在 C# 中,异常处理是一个非常重要的概念,它可以让我们在程序发生错误时进行有效的处理,使程序具备更好的鲁棒性。C# 异常处理机制基于 try-catch-finally 语句块,其基本用法如下: try {// 可能会抛出异常的代码 } cat…...
【0187】客户端身份验证配置文件视图之pg_hba_file_rules
文章目录 1. 客户端身份验证配置文件视图2. 视图效果相关阅读: 【0179】配置PostgreSQL以允许远程连接 【0180】PG内核通过pg_hba.conf完成客户端认证(1) 【0181】PG内核通过pg_hba.conf完成客户端认证(2)...
模糊层次分析法(FAHP)Python实现
文章目录 理论基础三角模糊数概念参考 Python源码测试 理论基础 \quad 模糊层次分析法( F A H P FAHP FAHP)将模糊理论( F u z z y S e t Fuzzy Set FuzzySet)嵌入到基本层次分析法( A H P AHP AHP)中。 A …...
gdb切换窗口焦点
为了辅助调试,一般会使用layout src,调起TUI显示代码: 然而这种情况下我们写命令很不方便,无法方便地使用上一条命令、退格等。 按动上下左右方向键盘只会移动代码框,然而在伪终端下,可以用鼠标滚轮来上下…...
【Spring Security】 入门实战
文章目录 一、基本概念二、Spring Security第一个程序三、Spring Security没有生效四、修改默认账号密码(appliction.yml)五、修改默认账号密码(配置类)六、Spring Security的三个configure方法七、Spring Security的三种身份的验…...
SpringBoot的Interceptor拦截器的简介和实际使用
拦截器(Interceptor) 概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,用来动态拦截控制器方法的执行。 作用:拦截请求,在指定的方法调用前后,根据业务需要执行…...
5个面向Python高级开发者的技巧
使用这些用于自定义类行为、编写并发代码、管理资源、存储和操作数据以及优化代码性能的高级技术来探索 Python 的深度。 本文探讨了 Python 中的五个高级主题,它们可以为解决问题和提高代码的可靠性和性能提供有价值的见解和技术。从允许您在定义类时自定义类行为的…...
Nginx简介
Nginx是什么?可以做什么事情? Nginx是高性能的HTTP和反向代理的web服务器,处理高并发的能力十分强大,能经受高负载的考研,有报告表明能能支持高达50000个并发连接数。 特点 占有内存少:一万个长连接&…...
十五分钟带你学会 Electron
文章目录 什么是 Electron为什么要选择 Electron安装 Electron桌面CSDN实战Electron 基础配置Electron 进程主进程渲染进程主进程与渲染进程的区别主进程与渲染进程的通信 Electron 跨平台问题Electron 部署打包应用程序发布应用程序 Electron 跨端原理总结 什么是 Electron E…...
设计模式-结构型模式之桥接模式
2. 桥接模式 2.1. 模式动机 设想如果要绘制矩形、圆形、椭圆、正方形,我们至少需要4个形状类,但是如果绘制的图形需要具有不同的颜色,如红色、绿色、蓝色等,此时至少有如下两种设计方案: 第一种设计方案是为每一种形状…...
软件测试工程师为什么要写测试用例?
软件测试工程师为什么要写测试用例?相信从事软件测试行业的从业者来讲,测试用例并不陌生。因为测试用例不仅仅是一组简单的文档,它包含前提条件、输入、执行条件和预期结果等等重要内容,并且能够完成一定的测试目的和需求。下面本…...
【DAY40】VUE练习
DOS命令: DOS(Disk Operating System)是一种操作系统,它使用命令行界面(Command Prompt)进行交互。在 DOS 中,有一些常用的命令,可以用来定位目录、创建、删除、拷贝文件和目录&…...
AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...
MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
python爬虫——气象数据爬取
一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用: 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests:发送 …...
DeepSeek源码深度解析 × 华为仓颉语言编程精粹——从MoE架构到全场景开发生态
前言 在人工智能技术飞速发展的今天,深度学习与大模型技术已成为推动行业变革的核心驱动力,而高效、灵活的开发工具与编程语言则为技术创新提供了重要支撑。本书以两大前沿技术领域为核心,系统性地呈现了两部深度技术著作的精华:…...
