C++11智能智能指针解析

        C++11 引入了 智能指针来解决手动管理动态内存的复杂性。它们能够自动管理堆内存,并在不再需要时自动释放,避免内存泄漏和悬空指针问题。C++11 提供了三种主要的智能指针类型:std::unique_ptrstd::shared_ptrstd::weak_ptr

1. std::unique_ptr:独占所有权

std::unique_ptr 是一种 独占所有权 的智能指针,它保证同一时间只能有一个智能指针拥有对象的所有权。这意味着对象只能被一个 unique_ptr 所管理,不能被多个指针共享。使用 std::unique_ptr 的场景通常是当一个对象的生命周期只需要在单个作用域内管理时。

特点
  • 独占所有权:不能复制,只能移动。
  • 生命周期:当 unique_ptr 离开其作用域时,会自动销毁并释放所管理的对象。
  • 轻量、高效:由于不涉及引用计数,unique_ptr 的效率通常比 shared_ptr 更高。
示例
#include <memory>
#include <iostream>struct MyClass {MyClass() { std::cout << "MyClass constructed\n"; }~MyClass() { std::cout << "MyClass destroyed\n"; }
};int main() {std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>(); // 自动管理动态分配的 MyClass// std::unique_ptr<MyClass> ptr2 = ptr1; // 错误,不能复制 unique_ptrstd::unique_ptr<MyClass> ptr2 = std::move(ptr1); // 正确,通过移动所有权// ptr1 现在为空
}
重要操作
  • 创建智能指针:使用 std::make_unique<T>() 动态分配内存并返回 std::unique_ptr<T>,避免手动使用 new
  • 移动所有权:使用 std::move 进行所有权转移,不能复制。

2. std::shared_ptr:共享所有权

std::shared_ptr 实现了 共享所有权,即可以有多个智能指针共同拥有同一个对象。对象的生命周期会被多个 shared_ptr 共享,只有当最后一个 shared_ptr 被销毁时,托管的对象才会被释放。

特点
  • 共享所有权:多个 shared_ptr 可以指向同一个对象。
  • 引用计数shared_ptr 维护一个引用计数器,当指向对象的所有 shared_ptr 被销毁时,引用计数器变为 0,对象会自动释放。
  • 适用场景:多个对象需要共享同一个资源时,例如在复杂的资源共享或对象关系管理中使用。
示例
#include <memory>
#include <iostream>struct MyClass {MyClass() { std::cout << "MyClass constructed\n"; }~MyClass() { std::cout << "MyClass destroyed\n"; }
};int main() {std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();{std::shared_ptr<MyClass> ptr2 = ptr1; // 引用计数增加std::cout << "Reference count: " << ptr1.use_count() << "\n"; // 输出 2}// 离开作用域,ptr2 被销毁,引用计数减少为 1std::cout << "Reference count: " << ptr1.use_count() << "\n"; // 输出 1
}
重要操作
  • 创建智能指针:使用 std::make_shared<T>() 创建共享指针,比手动分配内存效率更高。
  • 引用计数:使用 use_count() 查看当前引用计数。
注意事项
  • 循环引用问题:如果两个或多个 shared_ptr 对象形成循环引用(即 A 指向 B,B 又指向 A),那么即使引用计数变为 0,内存也不会被释放,导致内存泄漏。为了解决这个问题,引入了 std::weak_ptr

3. std::weak_ptr:弱引用

std::weak_ptr 是为了防止 std::shared_ptr 之间的 循环引用问题 而设计的。weak_ptr 并不影响引用计数,不拥有对象的所有权。它只是一个观察者,可以访问由 shared_ptr 管理的对象。如果 shared_ptr 管理的对象已经被释放,那么 weak_ptr 变为无效。

特点
  • 不增加引用计数weak_ptr 只是持有对象的引用,不会影响 shared_ptr 的引用计数。
  • 生命周期观察:使用 weak_ptr 观察对象的生命周期,可以通过 lock() 函数将 weak_ptr 转换为 shared_ptr,安全地访问对象。
示例
#include <memory>
#include <iostream>struct MyClass {MyClass() { std::cout << "MyClass constructed\n"; }~MyClass() { std::cout << "MyClass destroyed\n"; }
};int main() {std::shared_ptr<MyClass> sptr = std::make_shared<MyClass>();std::weak_ptr<MyClass> wptr = sptr; // weak_ptr 引用 sptr,但不增加引用计数std::cout << "Reference count: " << sptr.use_count() << "\n"; // 输出 1if (auto shared = wptr.lock()) {std::cout << "Object is still alive\n";} else {std::cout << "Object has been destroyed\n";}sptr.reset(); // 释放 shared_ptr 管理的对象if (auto shared = wptr.lock()) {std::cout << "Object is still alive\n";} else {std::cout << "Object has been destroyed\n";}
}

shared_ptr 内部维护的关键成员变量:

1. 指向对象的原始指针(Managed Object Pointer)

  • 这是指向实际动态分配对象的原始指针。它是 shared_ptr 管理的对象,shared_ptr 使用这个指针来访问对象。
  • 例如:T* ptr,它指向动态分配的对象(例如通过 make_sharednew 分配的对象)。

2. 控制块(Control Block)

  • 控制块shared_ptr 背后维护的一个数据结构,包含了引用计数、弱引用计数以及其他额外的信息。控制块并不与所管理的对象共享地址,它是 shared_ptr 机制中的核心,包含多个关键数据:
    • 引用计数(Reference Counter)
      • 一个用于记录有多少个 shared_ptr 实例同时引用该对象的计数器,称为共享引用计数。当创建一个新的 shared_ptr 实例指向相同对象时,该计数器递增;当 shared_ptr 被销毁或重置时,该计数器递减。
      • 当引用计数降为 0 时,托管的对象会被销毁。
    • 弱引用计数(Weak Reference Counter)
      • 另一个计数器,记录有多少个 weak_ptr 实例引用该对象。weak_ptr 不会影响共享引用计数,但会影响控制块的生命周期。
      • 当共享引用计数和弱引用计数都降为 0 时,控制块本身也会被销毁。
    • 自定义删除器(Custom Deleter)
      • shared_ptr 可以绑定一个自定义删除器,用于控制对象的销毁方式。如果你不指定删除器,shared_ptr 会使用默认的 delete 运算符释放对象。
      • 删除器保存在控制块中,当最后一个 shared_ptr 离开作用域时,它会调用这个删除器来销毁对象。

3. 弱引用管理(Weak Pointer Management)

  • shared_ptrweak_ptr 的结合使用涉及到对控制块中弱引用计数的维护。weak_ptr 不会增加对对象的共享引用计数,但会使控制块的弱引用计数增加。这样,即使没有任何 shared_ptr 实例指向对象,控制块依然存在,直到所有 weak_ptr 也被销毁。

具体控制块结构的解释

可以将控制块理解为包含以下信息的结构体(这是一个概念上的简化):

struct ControlBlock {int shared_count;   // 共享引用计数int weak_count;     // 弱引用计数void(*deleter)(T*); // 自定义删除器函数指针
};

每次创建 shared_ptr 时,shared_count 会增加,而 weak_count 仅在创建 weak_ptr 时增加。

控制块与对象的关系

  • 独立于对象的存储:控制块是一个独立的数据结构,和对象的内存分配是分开的。当使用 make_shared 时,对象和控制块可能会被分配到同一块内存区域中,但它们的作用是不同的。
  • 生命周期管理:当 shared_count 变为 0 时,shared_ptr 管理的对象会被销毁;当 weak_count 也为 0 时,控制块才会被销毁。

shared_ptr 循环引用的例子

#include <iostream>
#include <memory>struct B;struct A {std::shared_ptr<B> ptrB;  // A持有B的shared_ptr~A() { std::cout << "A destroyed\n"; }
};struct B {std::shared_ptr<A> ptrA;  // B持有A的shared_ptr~B() { std::cout << "B destroyed\n"; }
};int main() {std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->ptrB = b;  // A拥有Bb->ptrA = a;  // B拥有A// 循环引用导致A和B永远不会被销毁return 0;
}

在这个例子中:

  • A 持有一个指向 Bshared_ptr,而 B 也持有一个指向 Ashared_ptr
  • 由于 shared_ptr 会增加引用计数,ab 的引用计数永远无法减为 0,导致它们的析构函数不会被调用,从而造成内存泄漏。

解决循环引用:使用 std::weak_ptr

要解决循环引用问题,可以将其中一个 shared_ptr 替换为 std::weak_ptrweak_ptr 不会影响引用计数,因此可以打破循环引用。

解决后的代码

#include <iostream>
#include <memory>struct B;struct A {std::shared_ptr<B> ptrB;  // A持有B的shared_ptr~A() { std::cout << "A destroyed\n"; }
};struct B {std::weak_ptr<A> ptrA;    // B持有A的weak_ptr,打破循环引用~B() { std::cout << "B destroyed\n"; }
};int main() {std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->ptrB = b;  // A拥有Bb->ptrA = a;  // B弱引用A,不增加引用计数// 正常销毁A和B,输出"A destroyed"和"B destroyed"return 0;
}

关键点

  • BA 的引用改为 std::weak_ptrweak_ptr 不增加引用计数,不会阻止对象的销毁。
  • 在需要使用 weak_ptr 指向的对象时,可以调用 lock() 方法将其转换为 shared_ptr,如果对象已被销毁,则返回 nullptr

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

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

相关文章

使用 Light Chaser 进行大屏数据可视化

引言 在当今数据驱动的世界中&#xff0c;数据可视化变得越来越重要。Light Chaser 是一款基于 React 技术栈的大屏数据可视化设计工具&#xff0c;通过简单的拖拽操作&#xff0c;你可以快速生成漂亮、美观的数据可视化大屏和看板。本文将介绍如何使用 Light Chaser 进行数据…

ENV | docker 安装使用(简单实操版)

1. 详细步骤 1.1 安装 sudo apt update sudo apt install docker.io1.2 验证&#xff08;可跳过&#xff09; docker -v1.3 使用 1.3.1 拉取镜像 # 镜像源&#xff0c;如使用腾讯云服务器&#xff0c;可使用 https://mirror.ccs.tencentyun.com docker pull xxx1.3.2 运行…

828华为云征文|部署在线文档应用程序 CodeX Docs

828华为云征文&#xff5c;部署在线文档应用程序 CodeX Docs 一、Flexus云服务器X实例介绍二、Flexus云服务器X实例配置2.1 重置密码2.2 服务器连接2.3 安全组配置2.4 Docker 环境搭建 三、Flexus云服务器X实例部署 CodeX Docs3.1 CodeX Docs 介绍3.2 CodeX Docs 部署3.3 CodeX…

RabbitMQ应用

RabbitMQ 共提供了7种⼯作模式, 进⾏消息传递 一、七种模式的概述 1、Simple(简单模式) P:生产者,就是发送消息的程序 C:消费者,就是接收消息的程序 Queue:消息队列,类似⼀个邮箱, 可以缓存消息; ⽣产者向其中投递消息, 消费者从其中取出消息 特点: ⼀个⽣产者P,⼀…

小米2025届软件开发工程师(C/C++/Java)(编程题AK)

选择题好像也是25来个 编程题 T1 题目描述 小明喜欢解决各种数学难题。一天&#xff0c;他遇到了一道有趣的题目:他需要帮助他的朋友们完成一个排序任务。小明得到两个长度为 n 的数组a[]和b[]。他可以在两个数组对应位置进行交换&#xff0c;即选定一个位置 i &#xff0c…

hrnet训练的pt模型结合目标检测进行关键点识别的更准确前向推理

本篇在将图像输入hrnet识别之前先进行目标检测来确定识别的位置&#xff0c;让识别更加精准。 本段代码设置了一个区域框BOX&#xff0c;让人走入区域内才开始检测&#xff0c;适用于考核等场景&#xff0c;也可以直接去掉BOX也是一样的效果。若画面背景中有多个行人&#xff0…

《pyqt+open3d》open3d可视化界面集成到qt中

《pyqtopen3d》open3d可视化界面集成到qt中 一、效果显示二、代码三、资源下载 一、效果显示 二、代码 参考链接 main.py import sys import open3d as o3d from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget from PyQt5.QtGui import QWindow from PyQt5.Qt…

App模拟下载场景的demo

摘要 目的&#xff1a;提供一个稳定的下载场景&#xff0c;可以手动触发和定时触发下载&#xff0c;每次下载相同大小文件&#xff0c;研究下载场景的功耗影响 原理&#xff1a;把电脑当做服务器&#xff0c;手机测试App固定下载电脑存放的某个XXXMB的大文件&#xff0c;基于…

C语言进阶版第14课—内存函数

文章目录 1. memcpy函数的使用和模拟实现1.1 memcpy函数的使用1.2 模拟实现memcpy函数 2. memmove函数的使用和模拟实现2.1 memmove函数的使用2.2 memmove函数的模拟实现 3. memset函数4. memcmp函数 1. memcpy函数的使用和模拟实现 1.1 memcpy函数的使用 memcpy函数的原形voi…

英语音标与重弱读

英语中&#xff0c;比较重要的是音标。但事实上&#xff0c;我们对音标的学习还是比较少的&#xff0c;对它的理解也是比较少的。 一、音标 2个半元音 [w][j] 5个长元音&#xff1a;[i:] [ə:] [ɔ:] [u:] [ɑ:] 7个短元音&#xff1a;[i] [ə] [ɔ] [u] [] [e] [ʌ] 8个双元音…

leetcode-32. 最长有效括号

题目描述 给你一个只包含 ( 和 ) 的字符串&#xff0c;找出最长有效&#xff08;格式正确且连续&#xff09;括号 子串 的长度。 示例 1&#xff1a; 输入&#xff1a;s "(()" 输出&#xff1a;2 解释&#xff1a;最长有效括号子串是 "()"示例 2&…

压力测试指南-压力测试基础入门

压力测试基础入门 在当今快速迭代的软件开发环境中&#xff0c;确保应用程序在高负载情况下仍能稳定运行变得至关重要。这正是压力测试大显身手的时刻。本文将带领您深入了解压力测试的基础知识&#xff0c;介绍实用工具&#xff0c;并指导您设计、执行压力测试&#xff0c;最…

ffmpeg 结合 opencv 显示ps流文件

存储的ps 流文件如何显示 使用ffmpeg 和 opencv 做demo 结合opencv 和 ffmpeg 显示ps文件 // showps.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 //#include <iostream> #include <opencv2/opencv.hpp>extern "C" { #inc…

【openwrt-21.02】T750 openwrt switch划分VLAN之后网口插拔状态异常问题分析及解决方案

Openwrt版本 NAME="OpenWrt" VERSION="21.02-SNAPSHOT" ID="openwrt" ID_LIKE="lede openwrt" PRETTY_NAME="OpenWrt 21.02-SNAPSHOT" VERSION_ID="21.02-snapshot" HOME_URL="https://openwrt.org/" …

第100+26步 ChatGPT学习:概率校准 Bayesian Binning into Quantiles

基于Python 3.9版本演示 一、写在前面 最近看了一篇在Lancet子刊《eClinicalMedicine》上发表的机器学习分类的文章&#xff1a;《Development of a novel dementia risk prediction model in the general population: A large, longitudinal, population-based machine-learn…

【2】图像视频的加载和显示

文章目录 【2】图像视频的加载和显示一、代码在哪写二、创建和显示窗口&#xff08;一&#xff09;导入OpenCV的包cv2&#xff08;二&#xff09;创建窗口&#xff08;三&#xff09;更改窗口大小 & 显示窗口&#xff08;四&#xff09;等待用户输入补充&#xff1a;ord()函…

Gateway和VirtualService

在 Istio 服务网格中&#xff0c;Gateway 和 VirtualService 是两个关键的配置对象&#xff0c;它们分别用于定义入站流量的接入点和路由规则。下面详细介绍这两个配置对象的功能及其相互关系。 Gateway Gateway 是 Istio 中用于定义入站流量接入点的配置对象。它描述了外部流…

正则表达式的使用规则

1.介绍 正则表达式&#xff08;Regular Expression&#xff09;是一种强大的文本模式匹配工具&#xff0c;它使用单个字符串来描述、匹配一系列符合某个句法规则的字符串搜索模式。正则表达式可以用于文本搜索、文本替换等场景。 2.基本语法 正则表达式由普通字符&#xff0…

vue框架学习-- 父子页面 参数方法调用

一、父组件向子组件传递参数 在Vue中&#xff0c;父组件向子组件传递参数是一种非常常见的通信方式&#xff0c;这通常通过props来实现。props允许父组件向子组件传递数据&#xff0c;并且这些数据是单向的&#xff0c;即子组件不能直接修改由父组件传递的数据。但是&#xff…

【Unity踩坑】Unity更新Google Play结算库

一、问题描述&#xff1a; 在Google Play上提交了app bundle后&#xff0c;提示如下错误。 我使用的是Unity 2022.01.20f1&#xff0c;看来用的Play结算库版本是4.0 查了一下文档&#xff0c;Google Play结算库的维护周期是两年。现在需要更新到至少6.0。 二、更新过程 1. 下…