C++智能指针2——unique_ptr和weak_ptr

unique_ptr

一个unique_ptr“拥有”它所指向的对象。

与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定对象。

当unique_ptr被销毁时,它所指向的对象也被销毁。

和shared_ptr 不同,没有类似make_shared的标准库函数返回一个unique ptr

当我们定义一个unique_ptr时,需要将其绑定到一个new返回的指针

类似shared_ptr,初始化unique_ptr必须采用直接初始化形式:

unique_ptr<double> p1; // 可以指向一个double的unique_ptr
unique_ptr<int> p2(new int(42));// p2指向一个值为 42的int

由于一个unique_ptr拥有它指向的对象,因此unique_ptr不支持普通的拷贝或赋值操作:

unique_ptr<string> pl(new string("Stegosaurus"));
unique_ptr<string> p2(p1);// 错误:unique_ptr不支持拷贝unique_ptr<string> p3;
p3 = p2; //错误:unique_ptr不支持赋值
unique_ptr操作
unique_ptr<T>u1空智能指针,可以指向类型为T的对象
p将p作为一个条件判断,若p指向一个对象,则为true
*p解引用p,获得它指向的对象
p->mem等价于(*p).mem
p.get()返回p中保存的指针。要小心使用,若智能指针释放了其对象,返回的指针所指的对象也就消失了

swap(p,q)

p.swap(q)

交换p和q的指针
unique ptr<T, D> u2空unique_ptr,可以指向类型为T。u1会使用delete来释放它的指针
unique_ptr<T, D> u(d)空unique_ptr,指向类型为T的对象,用类型为D的对象d代替delete
u = nullptr释放u指向的对象,将u置为空
u.release()u放弃对指针的控制权
u.reset(q)
u.reset (nullptr)
如果提供了内置指针q,令u指向这个对象;否则将u置为空
u.reset()释放u指向的对象

虽然我们不能拷贝或赋值unique_ptr,但可以通过调用release或reset 将指针的所有权从一个(非const) unique_ptr转移给另一个unique:

// 将所有权从 pl(指向 string Stegosaurus)转移给p2
unique_ptr<string>p2(pl.release());// release将p1置为空
unique_ptr<string> p3(new string("Trex"));
// 将所有权从p3转移给p2
p2.reset(p3,release());// reset释放了p2原来指向的内存

release 成员返回unique_ptr当前保存的指针并将其置为空。

因此,p2被初始化为p1原来保存的指针,而p1被置为空。

reset成员接受一个可选的指针参数,令unique_ptr重新指向给定的指针。如果unique_ptr不为空,它原来指向的对象被释放。

因此,对 p2 调用 reset 释放了用"Stegosaurus”初始化的string所使用的内存,将p3对指针的所有权转移给p2,并将p3置为空。

调用release 会切断 unique_ptr和它原来管理的对象间的联系。release返回的指针通常被用来初始化另一个智能指针或给另一个智能指针赋值。

在本例中,管理内存的责任简单地从一个智能指针转移给另一个。但是,如果我们不用另一个智能指针来保存release返回的指针,我们的程序就要负责资源的释放:

p2.release(); //错误:p2不会释放内存,而且我们丢失了指针
auto p = p2.release();// 正确,但我们必须记得 delete(p)

传递 unique_ptr 参数和返回 unique_ptr

不能拷贝 unique_ptr的规则有一个例外;我们可以拷贝或赋值一个将要被销毁的unique_ptr。最常见的例子是从函数返回一个unique_ptr:

unique_ptr<int> clone (int p) 
{
/正确:从int创建一个unique_ptr<int>
return unlque_ptr<int>(new int(p));}

还可以返回一个局部对象的拷贝:

unique_ptr<int> clone(int p)
{
unique_ptr<int> ret(new int (p));
return ret;
}

对于两段代码,编译器都知道爱返回的对象将要被销毁。在此情况下,编译器执行一种特殊的“拷贝”.

向后兼容:auto_ptr
 

标准库的较早版本包含了一个名为auto_ptr的类,它具有unique_ptr的部分特性,但不是全部。特别是,我们不能在容器中保存auto_ptr,也不能从函数中返回auto_ptr。
 

虽然auto_ptr仍是标准库的一部分,但编写程序时应该使用unique_ptr。

向unique_ptr传递删除器

类似 shared_ptr, unique_ptr 默认情况下用delete释放它指向的对象。

与shared_ptr一样,我们可以重载一个unique_ptr中默认的删除器。但是,unique_ptr管理删除器的方式与shared_ptr不同。

重载一个 unique_ptr 中的删除器会影响到 unique_ptr类型以及如何构造(或reset)该类型的对象。与重载关联容器的比较操作类似,我们必须在尖括号中unique_ptr指向类型之后提供删除器类型。

在创建或reset一个这种unique_ptr类型的对象时,必须提供一个指定类型的可调用对象(删除器):

std::unique_ptr 允许你提供一个自定义的删除器,这个删除器会在 unique_ptr 被销毁时调用。

以下是一个使用自定义删除器的 std::unique_ptr 的例子:

#include <iostream>  
#include <memory>  // 自定义删除器,这里我们简单地将 delete 替换为 cout 输出  
struct CustomDeleter {  void operator()(int* ptr) const {  std::cout << "Deleting int with value: " << *ptr << std::endl;  delete ptr;  }  
};  int main() {  // 使用自定义删除器创建 unique_ptr  std::unique_ptr<int, CustomDeleter> smartPtr(new int(5));  // 输出智能指针指向的值  std::cout << "Value of int: " << *smartPtr << std::endl;  // 当 smartPtr 超出作用域时,CustomDeleter 的 operator() 将被调用  // 这将输出 "Deleting int with value: 5" 并删除 int  return 0;  
}

在这个例子中,我们定义了一个 CustomDeleter 结构体,它重载了 operator() 以接受一个 int* 参数。当 std::unique_ptr 被销毁时,它会调用这个 operator() 来删除其指向的对象。在这个例子中,我们只是简单地输出了一个消息,并调用了 delete 来释放内存。但是你可以根据需要实现任何逻辑。

weak_ptr


weak ptr 是一种不控制所指向对象生存期的智能指针,它指一个由shared_ptr 管理的对象。

将一个 weak_ ptr 绑定到一个shared_ptr 不会改shared_ptr的引用计数。

一 旦最后一个指向对象的shared_ptr被销毁,对象就会释放。

即使有 weak_ptr指向对象,对象也还是会被释放。

因此,weak_ptr的名字住了这种智能指针“弱”共享对象的特点。

weak_ptr
weak_ptr<T>1空weak_ptr可以指向类型为T的对象
weak_ptr<T> w(sp)与shared ptr sp指向相同对象的weak_ptr。T必须能转换为sp指向的类型
w=pp可以是一个shared_ptr或一个weak_ptr。赋值后w与p共享对象
w.reset()将w置为空
w.use_count()与w共享对象的shared_ptr的数量
w.expired()若w.use_count()为0,返回true,否则返回false
w.lock()如果expired为true,返回一个空shared_ptr;否则返回一个指向w的对象的shared_ptr

当我们创建一个weak_ptr时,要用一个shared_ptr来初始化它

auto p =make_shared<int>(42);
weak_ptr<int> wp(p);//wp弱共享p;P的引用计数未改变

本例中wp和p指向相同的对象。由于是弱共享,创建wp不会改变p的引用计数;wp指向的对象可能被释放掉。

由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock。

此函数检查weak_ptr指向的对象是否仍存在。如果存在lock返回一个指向共享对象的shared_ptr。

与任何其他shared_ptr类似,只要此shared_ptr存在,它所指向的底层对象也就会一直存在。

例如:

if (shared_ptr<int> np = wp.lock()) 
{//如果np不为空则条件成立//在if中,np与p共享对象
}


在这段代码中,只有当lock调用返回true时我们才会进入语句体。在f中,使用np访问共享对象是安全的。

weak_ptr的用途

 weak_ptr 的主要用途是打破 shared_ptr 之间的循环引用,从而防止内存泄漏。

在 C++ 中,当使用 shared_ptr 管理动态分配的内存时,每个 shared_ptr 都会持有一个引用计数,该计数表示有多少个 shared_ptr 实例指向同一个对象。只有当最后一个 shared_ptr 被销毁或重置时,所指向的对象才会被删除。

然而,当两个或多个 shared_ptr 实例相互引用时,就会形成一个循环引用。这意味着每个 shared_ptr 都认为另一个 shared_ptr 仍然持有对象的引用,因此对象的引用计数永远不会降到零,即使逻辑上已经没有代码再需要这个对象。这会导致内存泄漏,因为对象永远不会被删除。

weak_ptr 就是为了解决这个问题而设计的。它是对一个由shared_ptr 管理的对象的弱引用,这意味着它不控制对象的生命周期。weak_ptr 不拥有所指向的对象,它不会增加对象的引用计数。相反,它观察 shared_ptr 的引用计数,并在最后一个 shared_ptr 被销毁时变得无效。

通过使用 weak_ptr 来替代 shared_ptr 中的一个或多个引用,可以打破循环引用。当循环中的一个 shared_ptr 不再存在时,即使其他 shared_ptr 通过 weak_ptr 仍然可以“观察”到对象,对象的引用计数也会减少到零,从而触发对象的删除。

举例

下面是一个具体的例子,展示了std::weak_ptr如何用于解决循环引用的问题。

假设我们有一个简单的ParentChild类,每个Parent对象可以有一个Child对象,每个Child对象都有一个指向其Parent的指针。如果我们使用shared_ptr来管理这些对象的生命周期,就可能会遇到循环引用的问题。

#include <iostream>  
#include <memory>  class Child;  class Parent {  
public:  std::shared_ptr<Child> child;  ~Parent() {  std::cout << "Parent destroyed" << std::endl;  }  
};  class Child {  
public:  // 使用 weak_ptr 而不是 shared_ptr 来避免循环引用  std::weak_ptr<Parent> parent;  ~Child() {  std::cout << "Child destroyed" << std::endl;  }  
};  int main() {  // 创建 Parent 和 Child 对象,并设置相互引用  auto parent = std::make_shared<Parent>();  auto child = std::make_shared<Child>();  parent->child = child;  child->parent = parent; // 使用 weak_ptr 而不是 shared_ptr  // 此时,parent 和 child 相互引用,但由于 child 使用的是 weak_ptr,不会增加 Parent 对象的引用计数  // 当 parent 和 child 超出作用域并被销毁时...  // ...由于 child 使用的是 weak_ptr,它不会阻止 Parent 对象的销毁  // ...Parent 对象首先被销毁,然后 Child 对象也被销毁  // 输出 "Parent destroyed",然后输出 "Child destroyed"  // 证明即使存在相互引用,对象也能被正确删除,没有内存泄漏  return 0;  
}

在这个例子中,Parent 类有一个指向 Child 的 shared_ptr 成员,而 Child 类有一个指向 Parent 的weak_ptr 成员。当 parent 和 child 超出作用域时,由于 child 使用是 weak_ptr,它不会增加 Parent 对象的引用计数。因此,当 parent 被销毁时,没有其他的 shared_ptr 指向 Parent 对象,所以 Parent 对象也会被销毁。之后使 child 的 weak_ptr 成员仍然“观察”着已经销毁的 Parent 对象,它也不会阻止 Child 对象的销毁。最终,Child 对象也会被正确删除。

通过使用 std::weak_ptr,我们打破了 Parent 和 Child 之间的循环引用,确保了即使存在相互引用,对象的生命周期也能被正确管理,从而避免了内存泄漏。

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

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

相关文章

【双指针】两数之和|| 输入有序数组

两数之和|| 输入有序数组 给你一个下标从 1 开始的整数数组 numbers &#xff0c;该数组已按 非递减顺序排列 &#xff0c;请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] &#xff0c;则 1 < index1 …

在Linux系统上实现TCP(socket)通信

一.什么TCP TCP&#xff08;传输控制协议&#xff09;是一种面向连接的、可靠的、基于字节流的传输层通信协议。 二.TCP通信流程 三. TCP 服务器端 1 创建socket int sockfd socket(AF_INET, SOCK_STREAM, 0); //SOCK_STREAM tcp通信2 绑定(bind) struct sockaddr_in myad…

Ubuntu下无法获得锁 / 检测到系统程序错误 / E: Could not get lock /var/lib/apt/lists/lock

这里写自定义目录标题 Ubuntu下无法获得锁 错误 / E: Could not get lock /var/lib/apt/lists/lock Ubuntu下无法获得锁 错误 / E: Could not get lock /var/lib/apt/lists/lock 1、E: Could not get lock /var/lib/apt/lists/lock - open (11: Recource temporarily unavaila…

【双指针】删除有序数组中的重复项Ⅱ

给你一个有序数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使得出现次数超过两次的元素只出现两次 &#xff0c;返回删除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成 示例 1&…

C++实现幻方实验

我们这个实验目的是实现大于2的奇数的n阶幻方 根据上述的例子我们可以看到一些规律&#xff0c;显示1放在最上方中间的位置&#xff0c;然后向右上方延申&#xff0c;在达到n这个数字时&#xff0c;停止延申&#xff0c;然后在n的下方开始n1的新一轮延申。明白了原理之后就很容…

计算机专业,不擅长打代码,考研该怎么选择?

考研其实和你的代码能力关系不大 所以在选学校以前可以看看有哪些学校复试是要求上机撸代码的&#xff0c;可能会要求比较严 初试真的不用担心代码问题&#xff0c;我也是基本零编程能力就开始备考考研的... 本人双非科班出身备考408成功上岸&#xff0c;在这里也想给想考40…

css面试题--定位与浮动

1、为什么需要清除浮动&#xff1f; 在非IE浏览器下&#xff0c;容器不设高度且子元素浮动时&#xff0c;容器高度不能被内容撑开&#xff0c;内容会溢出到容器外面而影响布局。这种现象被称为浮动。 浮动的原理&#xff1a;浮动元素脱离文档流&#xff0c;不占用空间&#xff…

在 JavaScript或Typescript 中编写异步构造函数的正确方法

参考&#xff1a;The Proper Way to Write Async Constructors in JavaScript - DEV Community

使用 wangeditor 解析富文本并生成目录与代码块复制功能

在 Web 开发中&#xff0c;经常需要使用富文本编辑器来编辑和展示内容。wangeditor 是一个强大的富文本编辑器&#xff0c;提供了丰富的功能和灵活的配置&#xff0c;但是官方并没有提供目录导航和代码块的复制功能&#xff0c;所以我自己搞了一个 <template><div cla…

5个超好用的Python工具,赶紧码住!

Python开发软件可根据其用途不同分为两种&#xff0c;Python代码编辑器和Python集成开发工具&#xff0c;两者配合使用极大的提高Python开发人员的编程效率。掌握调试、语法高亮、Project管理、代码跳转、智能提示、自动完成、单元测试、版本控制等操作。 Python常用工具&…

小白新手学习 Python 使用哪个 Linux 系统更好?

对于小白新手学习Python&#xff0c;选择哪个Linux系统是一个很重要的问题&#xff0c;因为不同的Linux发行版&#xff08;distribution&#xff09;有着不同的特点、优势和适用场景。在选择时&#xff0c;需要考虑到易用性、学习曲线、社区支持等因素。 Ubuntu Ubuntu 是一个…

分布式系统中的唯一ID生成方法

通常在分布式系统中&#xff0c;有生成唯一ID的需求&#xff0c;唯一ID有多种实现方式。我们选择其中几种&#xff0c;简单阐述一下实现原理、适用场景、优缺点等信息。 目录 数据库多主复制UUID工单服务器雪花算法总结 数据库多主复制 数据库通常有自增属性&#xff0c;在单机…

CSS 实现无限波浪边框卡片

CSS 实现无限波浪边框卡片 效果展示 鼠标悬停效果&#xff0c;底部色块的边框是无限滚动的波浪 鼠标没有悬停效果 CSS 知识点 CSS 基础知识回顾使用 radial-gradient 实现波浪边框使用 anumate 属性实现波浪边框动画和控制动画运动 波浪实现原理 波浪边框的实现思路其…

dayjs 判断是否今天、本周内、本年内、本年外显示周几、月份等

效果: 判断是否今天需从 dayjs 中引入 isToday 插件&#xff1b; 判断是否两个日期之间需从 dayjs 中引入 isBetween 插件 import dayjs from dayjs import isToday from dayjs/plugin/isToday import isBetween from dayjs/plugin/isBetween// 注册插件 dayjs.extend(isBet…

浅谈对线程的理解

一、线程的概念 1、线程的概念 在Python中&#xff0c;想要实现多任务还可以使用多线程来完成。 2、为什么使用多线程&#xff1f; 进程是分配资源的最小单位 , 一旦创建一个进程就会分配一定的资源 , 就像跟两个人聊QQ就需要打开两个QQ软件一样是比较浪费资源的 . 线程是…

DP:子数组模型

一、最大子数组和 . - 力扣&#xff08;LeetCode&#xff09; 二、环形子数组的最大和 . - 力扣&#xff08;LeetCode&#xff09; class Solution { public:int maxSubarraySumCircular(vector<int>& nums) {//动态规划思想解决 //环形数组问题&#xff0c;尝试转…

01-Git 快速入门

https://learngitbranching.js.org/?localezh_CN在线练习git 1. Git 安装好Git以后, 先检查是否已经绑定了用户名和邮箱 git config --list再检查C:\Users\xxx.ssh 下是否存在 id_rsa.pub , 存在的话复制其内容到 GitHub 的 SSH KEY 中 没有这一步, PUSH操作的时候会报错:…

土壤墒情监测系统:洞察土壤水分奥秘

TH-TS400土壤墒情监测系统&#xff0c;作为现代农业科技的重要组成部分&#xff0c;已经成为农业生产过程中不可或缺的一环。该系统通过先进的传感器技术和数据处理能力&#xff0c;能够实时监测土壤的水分状况&#xff0c;为农业生产提供精准、可靠的数据支持。本文将从系统构…

存储设备与网络监控运维实践

随着企业数据量的不断增长和网络架构的日益复杂&#xff0c;存储设备和网络设施的稳定运行变得至关重要。为了确保这些关键组件的性能和可用性&#xff0c;实施全面的监控策略是运维团队的首要任务。本文旨在为运维团队提供存储设备监控、网络流量分析以及网络配置管理方面的参…

如何在debian12.5上安装snap和docker

在Debian 12.5上安装Snap和Docker的步骤如下&#xff1a; 1. 更新系统包列表&#xff1a; sudo apt update sudo apt upgrade 2. 安装 Snap&#xff1a; Debian 12.5 默认已经启用了 Snap 支持&#xff0c;所以你可以直接通过 apt 来安装 Snap。 sudo apt install snapd …