C++参悟:内存管理 shared_ptr

内存管理 shared_ptr

  • 一、概述
  • 二、成员函数
    • 1. 构造函数
    • 2. 析构函数
    • 3. 修改器
      • 1. reset
      • 2. swap
    • 4. 观察器
      • 1. get
      • 2. use_count
      • 3. operator bool

一、概述

std::shared_ptr 是通过指针保持对象共享所有权的智能指针。多个 shared_ptr 对象可占有同一对象。下列情况之一出现时销毁对象并解除这个指针指向的分配内存:

  • 最后剩下的占有对象的 shared_ptr 被销毁;
  • 最后剩下的占有对象的 shared_ptr 被通过 operator= 或 reset() 赋值为另一指针。

用 delete 表达式或在构造期间提供给 shared_ptr 的定制删除器销毁对象。

shared_ptr 能在存储指向一个对象的指针时共享另一对象的所有权。此特性能用于在占有其所属对象时,指向成员对象。存储的指针为 get() 、解引用及比较运算符所访问。被管理指针是在 use_count 抵达零时传递给删除器者。

shared_ptr 亦可不占有对象,该情况下称它为空 (empty) (空 shared_ptr 可拥有非空存储指针,若以别名使用构造函数创建它)。

shared_ptr 的所有特化满足可复制构造 (CopyConstructible) 、可复制赋值 (CopyAssignable) 和可小于比较 (LessThanComparable) 的要求并可按语境转换为 bool 。

多个线程能在 shared_ptr 的不同实例上调用所有成员函数(包含复制构造函数与复制赋值)而不附加同步,即使这些实例是副本,且共享同一对象的所有权。若多个执行线程访问同一 shared_ptr 而不同步,且任一线程使用 shared_ptr 的非 const 成员函数,则将出现数据竞争;原子函数的 shared_ptr 特化能用于避免数据竞争。

#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
#include <mutex>struct Base
{Base() { std::cout << "  Base::Base()\n"; }// 注意:此处非虚析构函数 OK~Base() { std::cout << "  Base::~Base()\n"; }
};struct Derived: public Base
{Derived() { std::cout << "  Derived::Derived()\n"; }~Derived() { std::cout << "  Derived::~Derived()\n"; }
};void thr(std::shared_ptr<Base> p)
{std::this_thread::sleep_for(std::chrono::seconds(1));std::shared_ptr<Base> lp = p; // 线程安全,虽然自增共享的 use_count{static std::mutex io_mutex;std::lock_guard<std::mutex> lk(io_mutex);std::cout << "local pointer in a thread:\n"<< "  lp.get() = " << lp.get()<< ", lp.use_count() = " << lp.use_count() << '\n';}
}int main()
{std::shared_ptr<Base> p = std::make_shared<Derived>();std::cout << "Created a shared Derived (as a pointer to Base)\n"<< "  p.get() = " << p.get()<< ", p.use_count() = " << p.use_count() << '\n';std::thread t1(thr, p), t2(thr, p), t3(thr, p);p.reset(); // 从 main 释放所有权std::cout << "Shared ownership between 3 threads and released\n"<< "ownership from main:\n"<< "  p.get() = " << p.get()<< ", p.use_count() = " << p.use_count() << '\n';t1.join(); t2.join(); t3.join();std::cout << "All threads completed, the last one deleted Derived\n";
}
//输出:Base::Base()Derived::Derived()
Created a shared Derived (as a pointer to Base)p.get() = 0x2299b30, p.use_count() = 1
Shared ownership between 3 threads and released
ownership from main:p.get() = 0, p.use_count() = 0
local pointer in a thread:lp.get() = 0x2299b30, lp.use_count() = 5
local pointer in a thread:lp.get() = 0x2299b30, lp.use_count() = 3
local pointer in a thread:lp.get() = 0x2299b30, lp.use_count() = 2Derived::~Derived()Base::~Base()
All threads completed, the last one deleted Derived

二、成员函数

1. 构造函数

2. 析构函数

~shared_ptr();

若 *this 占有对象且它是最后一个占有该对象的 shared_ptr ,则通过占有的删除器销毁对象。

析构后,与 *this 共享所有权的智能指针若存在,则报告比先前值少一的 use_count() 。

不同于 std::unique_ptr ,即使被管理指针为空也调用 std::shared_ptr 的删除器。

#include <memory>
#include <iostream>struct S {S() { std::cout << "S::S()\n"; }~S() { std::cout << "S::~S()\n"; }struct Deleter {void operator()(S* s) const {std::cout << "S::Deleter()\n";delete s;}};
};int main()
{auto sp = std::shared_ptr<S>{ new S, S::Deleter{} };auto use_count = [&sp](char c) {std::cout << c << ") use_count(): " << sp.use_count() << '\n';};use_count('A');{auto sp2 = sp;use_count('B');{auto sp3 = sp;use_count('C');}use_count('D');}use_count('E');sp.reset();use_count('F');
}/*
因为用 {} 括起来的语句其实就是一个函数栈,可以看成一个函数,执行完就会被销毁掉
因此 use_count() 的个数才会变化
*/
//输出:S::S()
A) use_count(): 1
B) use_count(): 2
C) use_count(): 3
D) use_count(): 2
E) use_count(): 1
S::Deleter()
S::~S()
F) use_count(): 0

3. 修改器

1. reset

void reset() noexcept; (1)template< class Y >
void reset( Y* ptr ); (2)template< class Y, class Deleter >
void reset( Y* ptr, Deleter d ); (3)template< class Y, class Deleter, class Alloc >
void reset( Y* ptr, Deleter d, Alloc alloc ); (4)

以 ptr 所指向的对象替换被管理对象。能可选地提供删除器 d ,之后在无 shared_ptr 对象占有该对象时以之销毁新对象。默认以 delete 表达式为删除器。始终选择对应提供类型的 delete 表达式,这是函数以使用分离的形参 Y 的模板实现的理由。

若 *this 已占有对象,且它是最后一个占有该对象的 shared_ptr ,则通过所占有的删除器销毁对象。

若 ptr 所指向的对象已被占有,则函数通常会导致未定义行为。

  1. 释放被管理对象的所有权,若存在。调用后, *this 不管理对象。等价于 shared_ptr().swap(*this); 。

2-4) 以 ptr 所指向对象替换被管理对象。 Y 必须是完整类型且可隐式转换为 T 。另外:

  1. 以 delete 表达式为删除器。合法的 delete 表达式必须可用,即 delete ptr 必须为良式,拥有良好定义行为且不抛任何异常。等价于 shared_ptr(ptr).swap(*this); 。

  2. 以指定的删除器 d 为删除器。 Deleter 必须对 T 类型可调用,即 d(ptr)必须为良构,拥有良好定义行为且不抛任何异常。 Deleter 必须可复制构造 (CopyConstructible) ,且其复制构造函数和析构函数必须不抛异常。等价于 shared_ptr(ptr, d).swap(*this); 。

  3. 同 (3) ,但额外地用 alloc 的副本分配内部使用的数据。 Alloc 必须是分配器 (Allocator) 。复制构造函数和析构函数必须不抛异常。等价于 shared_ptr(ptr, d, alloc).swap(*this); 。

#include <memory>
#include <iostream>struct Foo {Foo(int n = 0) noexcept : bar(n) {std::cout << "Foo: constructor, bar = " << bar << '\n';}~Foo() {std::cout << "Foo: destructor, bar = " << bar << '\n';}int getBar() const noexcept { return bar; }
private:int bar;
};int main()
{std::shared_ptr<Foo> sptr = std::make_shared<Foo>(1);std::cout << "The first Foo's bar is " << sptr->getBar() << "\n";// 重置,交与新的 Foo 实例// (此调用后将销毁旧实例)sptr.reset(new Foo);std::cout << "The second Foo's bar is " << sptr->getBar() << "\n";
}
// 输出:Foo: constructor, bar = 1
The first Foo's bar is 1
Foo: constructor, bar = 0
Foo: destructor, bar = 1
The second Foo's bar is 0
Foo: destructor, bar = 0

2. swap

void swap( shared_ptr& r ) noexcept;

交换 *this 与 r 的存储指针值与所有权。不调整引用计数,若它们存在。

4. 观察器

1. get

T* get() const noexcept;  (C++17) 

存储的指针。

shared_ptr 可能在存储指向一个对象的指针时共享另一对象的所有权。 get() 返回存储的指针,而非被管理指针。

#include <iostream>
#include <memory>
#include <string_view>void output(std::string_view msg, int const* pInt)
{std::cout << msg << *pInt << "\n";
}int main()
{int* pInt = new int(42);std::shared_ptr<int> pShared = std::make_shared<int>(42);output("Naked pointer ", pInt);// output("Shared pointer ", pShared); // 编译错误output("Shared pointer with get() ", pShared.get());delete pInt;
}// 输出:
Naked pointer 42
Shared pointer with get() 42

2. use_count

long use_count() const noexcept;

返回管理当前对象的不同 shared_ptr 实例(包含 this )数量。若无管理对象,则返回 ​0​ 。

多线程环境下, use_count 返回的值是近似的(典型实现使用 memory_order_relaxed 加载)

注意

常用使用包括

  • 与 ​0​ 比较。若 use_count 返回零,则智能指针为空且不管理对象(无论被存储指针是否为空)。多线程环境下,这不隐含被管理对象的析构函数已完成。
  • 与 1 比较。若 use_count 返回 1 ,则无其他拥有者。(被弃用成员函数 unique() 为此使用情况提供。)多线程环境中,这不隐含对象可以安全修改,因为先前拥有者对被管理对象的访问可能未完成,而因为新的共享拥有者可以同时引入,例如用 std::weak_ptr::lock 。
#include <memory> 
#include <iostream> void fun(std::shared_ptr<int> sp)
{std::cout << "fun: sp.use_count() == " << sp.use_count() << '\n'; 
}int main() 
{ auto sp1 = std::make_shared<int>(5);std::cout << "sp1.use_count() == " << sp1.use_count() << '\n'; fun(sp1);
}//输出:
sp1.use_count() == 1
fun: sp.use_count() == 2

3. operator bool

explicit operator bool() const noexcept;

检查 *this 是否存储非空指针,即是否有 get() != nullptr 。

注意:空 shared_ptr (其中 use_count() == 0 )可能存储能以 get() 访问的非空指针,例如若它以别名使用构造函数创建。

#include <iostream>
#include <memory>void report(std::shared_ptr<int> ptr) 
{if (ptr) {std::cout << "*ptr=" << *ptr << "\n";} else {std::cout << "ptr is not a valid pointer.\n";}
}int main()
{std::shared_ptr<int> ptr;report(ptr);ptr = std::make_shared<int>(7);report(ptr);
}//输出:
ptr is not a valid pointer.
*ptr=7

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

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

相关文章

大模型基础架构入门

大模型架构 Prefix Decoder 和 Causal Decoder 和 Encoder-Decoder 区别 在于 attention mask不同&#xff1a; https://zhuanlan.zhihu.com/p/626310493 为何现在的大模型大部分是Decoder only结构&#xff1f; https://www.zhihu.com/question/588325646/answer/335725261…

代码随想录算法训练营第四十四天 | 518. 零钱兑换 II、377. 组合总和 Ⅳ

题目链接&#xff1a;518. 零钱兑换 II 文章讲解&#xff1a;代码随想录 518. 零钱兑换 II讲解 视频讲解&#xff1a;装满背包有多少种方法&#xff1f;组合与排列有讲究&#xff01;| LeetCode&#xff1a;518.零钱兑换II 思路和解法 题目&#xff1a; 给你一个整数数组 c…

JVM-JVM中对象的结构

对象内存布局 对象里的三个区&#xff1a; 对象头&#xff08;Header&#xff09;&#xff1a;Java对象头占8byte。如果是数组则占12byte。因为JVM里数组size需要使用4byte存储。 标记字段MarkWord&#xff1a; 用于存储对象自身的运行时数据&#xff0c;它是synchronized实现轻…

STM32——OLED菜单

文章目录 一.补充二. 二级菜单代码 简介&#xff1a;首先在我的51 I2C里面有OLED详细讲解&#xff0c;本期代码从51OLED基础上移植过来的&#xff0c;可以先看完那篇文章&#xff0c;在看这个&#xff0c;然后按键我是用的定时器扫描不会堵塞程序,可以翻开我的文章有单独的定时…

[2024]常用的conda指令

[2024]常用的conda指令 Hi&#xff0c;各位新年好&#xff0c;今天给大家介绍一下Python项目开发中常用的工具-Anaconda。 Anaconda指的是一个开源的Python发行版本&#xff0c;也是一个安装、管理Python相关包的软件。它自带了Python、Jupyter Notebook、Spyder等工具&#x…

MIPS指令集处理器设计(支持64条汇编指令)

一、题目背景和意义 二、国内外研究现状 (略) 三、MIPS指令集处理器设计与实现 (一).MIPS指令集功能性梳理 1.MIPS指令集架构 (1).mips基础指令集格式总结 MIPS是&#xff08;Microcomputer without interlocked pipeline stages&#xff09;[10]的缩写&#xff0c;含义是…

C++友元->全局函数做友元、类做友元、成员函数做友元

全局函数做友元代码&#xff1a; #include<iostream> using namespace std; #include<string> //建筑物类 class Building { //告诉编译器 goodGay全局函数 是 Building类的好朋友&#xff0c;可以访问类中的私有内容 friend void goodGay(Building * bu…

第14讲投票帖子详情实现

投票帖子详情实现 后端,根据id查询投票帖子信息&#xff1a; /*** 根据id查询* param id* return*/ GetMapping("/{id}") public R findById(PathVariable(value "id")Integer id){Vote vote voteService.getById(id);WxUserInfo wxUserInfo wxUserInf…

电商+支付双系统项目------设计数据库

这篇文章将详细介绍电商支付双系统项目的数据库设计。数据库在该项目中扮演着至关重要的角色&#xff0c;它负责存储和管理用户信息、商品数据、订单记录以及支付交易等关键数据。通过精心设计和优化数据库结构&#xff0c;可以实现高效的数据存储和检索&#xff0c;确保系统的…

JavaScript中的常见算法

一.排序算法 1.冒泡排序 冒泡排序比较所有相邻的两个项&#xff0c;如果第一个比第二个大&#xff0c;则交换它们。元素项向上移动至 正确的顺序&#xff0c;就好像气泡升至表面一样。 function bubbleSort(arr) {const { length } arrfor (let i 0; i < length - 1; i)…

详解自定义类型:枚举与联合体!

目录 ​编辑 一、枚举类型 1.枚举类型的声明 2.枚举类型的优点 3.枚举类型的使用 二、联合体类型(共用体&#xff09; 1.联合体类型的声明 2.联合体的特点 3.相同成员的结构体和联合体的对比 4.联合体大小的计算 5.用联合体判断大小端 三.完结散花 悟已往之不谏&…

【Webpack】自动执行开发服务器 devServer

开发服务器&自动化 每次写完代码都需要手动输入指令才能编译代码&#xff0c;太麻烦了&#xff0c;我们希望一切自动化&#xff0c;即修改代码后服务器浏览器自动刷新。 1. 下载包 npm i webpack-dev-server -D2. 配置 webpack.config.js const path require("p…

深入浅出了解谷歌「Gemini大模型」发展历程

Google在2023年12月官宣了Gemini模型&#xff0c;随后2024年2月9日才宣布Gemini 1.0 Ultra正式对公众服务&#xff0c;并且开始收费。现在2024年2月14日就宣布了Gemini 1.5 Pro&#xff0c;史诗级多模态最强MoE首破100万极限上下文纪录&#xff01;&#xff01;&#xff01;Gem…

Grafana入门:从0开始打造动态仪表板

Grafana入门&#xff1a;从0开始打造动态仪表板 Grafana是一个开源的数据可视化和监控工具&#xff0c;它支持多种数据源&#xff0c;如Prometheus、InfluxDB、Elasticsearch等。通过Grafana&#xff0c;我们可以轻松地创建漂亮的仪表盘&#xff0c;实时监控应用程序和服务的性…

数据分析 — Pandas 数据加载、存储和清洗

目录 一、文件读取1、常见文件读取函数2、read_csv()3、read_table()4、read_excel()5、read_json()6、read_html()7、大文件读取 二、数据保存1、csv2、excel3、json4、html5、MySQL1、连接数据库2、MySQL 存储到本地3、本地存储到 MySQL 三、数据清洗1、处理缺失值1、判断数据…

Aster实现一台电脑当两台使——副屏使用独立win账号

前言&#xff1a;笔者每年回家&#xff0c;都面临着想要和小伙伴一起玩游戏&#xff0c;但小伙伴没有电脑/只有低配电脑的问题。与此同时&#xff0c;笔者自身的电脑是高配置的电脑&#xff0c;因此笔者想到&#xff0c;能否在自己的电脑上运行游戏&#xff0c;在小伙伴的电脑上…

LaTeX中的documentclass命令:指定文档的类型和整体布局

诸神缄默不语-个人CSDN博文目录 documentclass 是 LaTeX 中一个基础且重要的命令&#xff0c;用于定义文档的整体布局和样式。这个命令告诉 LaTeX 编译器文档是属于哪一类的&#xff0c;比如是文章、报告、书籍等&#xff0c;每一类都有其预定义的格式和结构。 文章目录 基本语…

怎么恢复电脑重装前的数据?介绍几种有效的方法

在日常生活和工作中&#xff0c;电脑已成为我们不可或缺的工具。然而&#xff0c;有时候我们会遇到一些突发情况&#xff0c;比如电脑系统崩溃需要重新安装系统。在这个过程中&#xff0c;我们可能会失去一些重要的数据&#xff0c;比如照片、文档、视频等。这些数据可能包含着…

基于springboot车辆充电桩管理系统源码和论文

随着信息化时代的到来&#xff0c;管理系统都趋向于智能化、系统化&#xff0c;车辆充电桩管理系统也不例外&#xff0c;但目前国内仍都使用人工管理&#xff0c;市场规模越来越大&#xff0c;同时信息量也越来越庞大&#xff0c;人工管理显然已无法应对时代的变化&#xff0c;…

马斯克评 OpenAI 视频模型,接地气又一针见血

马斯克评 OpenAI Sora 昨天&#xff0c;OpenAI 发布了首个视频生成模型 Sora。 一位 X&#xff08;前推特&#xff09;用户分享了 Sora 官网所展示的生成视频&#xff1a;一名女子在东京街头漫步。 该用户评论称&#xff1a;"OpenAI 今天宣布了 Sora&#xff0c;它使用混合…