C++共享指针shared_ptr的理解分享

2024-06-12 18:44

本文主要是介绍C++共享指针shared_ptr的理解分享,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

share_ptr是线程安全的吗

回答:

  • 如果多个线程同时拷贝同一个shared_ptr对象,不会有问题,因为shared_ptr的引用技术是线程安全的。
  • 如果多个线程同时修改同一个shared_ptr对象,不是线程安全的。
  • 如果多个线程同时读写shared_ptr指向的内存对象,不是线程安全的。

关键点

  • 引用计数的安全性:std::shared_ptr的引用计数是原子的,这意味着增加或减少引用计数的操作是线程安全的,即使这些操作在多个线程中同时进行也不会出现问题。
  • 赋值操作的不安全性:std::shared_ptr的赋值操作(包括拷贝构造和赋值运算符)涉及多个步骤,这些操作在多个线程中同时进行时可能不安全,因为它们不是原子的。
  • 对象访问的不安全性:std::shared_ptr不会为被管理的对象提供任何线程安全的保障。如果多个线程尝试读取或修改被管理对象的状态,必须使用锁或其他同步机制来确保线程安全。
  • 构造和析构的不安全性:如果多个线程同时创建或销毁std::shared_ptr实例,虽然引用计数的增减是线程安全的,但与对象关联的构造和析构操作可能需要额外的同步来避免竞态条件。

std::shared_ptr主要由一下关键部分组成

  • 控制块:存储引用计数(use_count:跟踪有多少std::shared_ptr实例指向同个对象)和弱引用计数(weak_count:跟踪有多少std::weak_ptr实例指向同一个控制块)。
  • 智能指针对象:持有指向控制块的指针和指向托管对象的指针。

核心操作

  • 构造和析构。
  • 复制构造和赋值。
  • 释放对象(当引用计数降到0时)。
  • 增加和减少引用计数。
  • 获取和设置指向的原始指针。

使用样例

#include <iostream>
#include <memory>struct BigObj {BigObj() {std::cout << "big object has been constructed" << std::endl;}~BigObj() {std::cout << "big object has been destructed" << std::endl;}
};void test_ref() {std::shared_ptr<BigObj> sp1 = std::make_shared<BigObj>();   // 调用了BigObj构造std::cout << sp1.use_count() << std::endl;  // 1std::shared_ptr<BigObj> sp2 = sp1;   std::cout << sp2.use_count() << std::endl;  // 2std::shared_ptr<BigObj> sp3 = sp2; std::cout << sp3.use_count() << std::endl;  // 3std::cout << sp1.use_count() << std::endl;  // 3    
}void test_ref1() {std::shared_ptr<BigObj> sp1 = std::make_shared<BigObj>();   // 调用了BigObj构造std::cout << sp1.use_count() << std::endl;  // 1{std::shared_ptr<BigObj> sp2 = sp1;  std::cout << sp1.use_count() << std::endl; // 2   }std::cout << sp1.use_count() << std::endl;  // 1BigObj* ptr = sp1.get(); sp1 = nullptr;std::cout << sp1.use_count() << std::endl;  // 0
}int main() {test_ref();   // 程序生命周期结束,即使引用计数不是0,智能指针也进行资源析构test_ref1();  // 当引用计数变为0,它会调用控制块中存储的析构函数来销毁资源。}

样例输出结果

big object has been constructed
1
2
3
3
big object has been destructed
big object has been constructed
1
2
1
big object has been destructed
0

shared_ptr的析构操作

  • 减少其内部控制块中的引用计数。
  • 如果引用计数变为0,它会调用控制块中存储的析构函数来销毁资源。
  • 最后,它会减少控制块的弱引用计数,并如果弱引用计数也变为0,那么整个控制块将被销毁。
    因此,当一个函数结束时,如果该函数内定义的std::shared_ptr是唯一引用某个对象的实例,那么该对象将在函数结束时被自动销毁。这是std::shared_ptr提供的一种自动资源管理机制,有助于防止内存泄漏和资源泄露。

shared_ptr简单代码示例

template<typename T>
class shared_ptr {
private:T* ptr; // 指向分配的内存资源size_t* ref_count; // 引用计数public:// 构造函数explicit shared_ptr(T* p = nullptr) : ptr(p), ref_count(new size_t(1)) {}// 拷贝构造函数shared_ptr(const shared_ptr<T>& other) : ptr(other.ptr), ref_count(other.ref_count) {(*ref_count)++;}// 移动构造函数shared_ptr(shared_ptr<T>&& other) noexcept : ptr(other.ptr), ref_count(other.ref_count) {other.ptr = nullptr;other.ref_count = nullptr;}// 析构函数~shared_ptr() {release();}// 重载赋值运算符shared_ptr<T>& operator=(const shared_ptr<T>& other) {if (this != &other) {release();ptr = other.ptr;ref_count = other.ref_count;(*ref_count)++;}return *this;}// 重载移动赋值运算符shared_ptr<T>& operator=(shared_ptr<T>&& other) noexcept {if (this != &other) {release();ptr = other.ptr;ref_count = other.ref_count;other.ptr = nullptr;other.ref_count = nullptr;}return *this;}// 获取指针指向的对象T* get() const { return ptr; }// 获取引用计数size_t use_count() const { return (ref_count != nullptr) ? *ref_count : 0; }// 重载箭头操作符T* operator->() const { return ptr; }// 解引用操作符T& operator*() const { return *ptr; }// 释放资源void release() {if (ref_count != nullptr) {(*ref_count)--;if (*ref_count == 0) {delete ptr;delete ref_count;}}}// 重置指针void reset(T* p = nullptr) {release();ptr = p;ref_count = new size_t(1);}
};

这篇关于C++共享指针shared_ptr的理解分享的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1055027

相关文章

C++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

C++中悬垂引用(Dangling Reference) 的实现

《C++中悬垂引用(DanglingReference)的实现》C++中的悬垂引用指引用绑定的对象被销毁后引用仍存在的情况,会导致访问无效内存,下面就来详细的介绍一下产生的原因以及如何避免,感兴趣... 目录悬垂引用的产生原因1. 引用绑定到局部变量,变量超出作用域后销毁2. 引用绑定到动态分配的对象,对象

深入理解Mysql OnlineDDL的算法

《深入理解MysqlOnlineDDL的算法》本文主要介绍了讲解MysqlOnlineDDL的算法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小... 目录一、Online DDL 是什么?二、Online DDL 的三种主要算法2.1COPY(复制法)

Linux挂载linux/Windows共享目录实现方式

《Linux挂载linux/Windows共享目录实现方式》:本文主要介绍Linux挂载linux/Windows共享目录实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录文件共享协议linux环境作为服务端(NFS)在服务器端安装 NFS创建要共享的目录修改 NFS 配

C++读写word文档(.docx)DuckX库的使用详解

《C++读写word文档(.docx)DuckX库的使用详解》DuckX是C++库,用于创建/编辑.docx文件,支持读取文档、添加段落/片段、编辑表格,解决中文乱码需更改编码方案,进阶功能含文本替换... 目录一、基本用法1. 读取文档3. 添加段落4. 添加片段3. 编辑表格二、进阶用法1. 文本替换2

Rust 智能指针的使用详解

《Rust智能指针的使用详解》Rust智能指针是内存管理核心工具,本文就来详细的介绍一下Rust智能指针(Box、Rc、RefCell、Arc、Mutex、RwLock、Weak)的原理与使用场景,... 目录一、www.chinasem.cnRust 智能指针详解1、Box<T>:堆内存分配2、Rc<T>:

C++中处理文本数据char与string的终极对比指南

《C++中处理文本数据char与string的终极对比指南》在C++编程中char和string是两种用于处理字符数据的类型,但它们在使用方式和功能上有显著的不同,:本文主要介绍C++中处理文本数... 目录1. 基本定义与本质2. 内存管理3. 操作与功能4. 性能特点5. 使用场景6. 相互转换核心区别

k8s搭建nfs共享存储实践

《k8s搭建nfs共享存储实践》本文介绍NFS服务端搭建与客户端配置,涵盖安装工具、目录设置及服务启动,随后讲解K8S中NFS动态存储部署,包括创建命名空间、ServiceAccount、RBAC权限... 目录1. NFS搭建1.1 部署NFS服务端1.1.1 下载nfs-utils和rpcbind1.1

C++右移运算符的一个小坑及解决

《C++右移运算符的一个小坑及解决》文章指出右移运算符处理负数时左侧补1导致死循环,与除法行为不同,强调需注意补码机制以正确统计二进制1的个数... 目录我遇到了这么一个www.chinasem.cn函数由此可以看到也很好理解总结我遇到了这么一个函数template<typename T>unsigned

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法