C++提供的智能指针 unique_ptr、shared_ptr、weak_ptr

C++提供的智能指针 unique_ptr、shared_ptr、weak_ptr

flyfish

C++提供的智能指针 unique_ptr、shared_ptr、weak_ptr

  • C++提供的智能指针 unique_ptr、shared_ptr、weak_ptr
      • 曾经的代码是这样写的
      • 示例
    • 1. `std::unique_ptr`
    • 2. `std::shared_ptr`
    • 3. `std::weak_ptr`
    • 循环引用 (写一个错误的例子和正确的例子)
        • 循环引用示例 错误的例子
        • 循环引用示例 正确的例子
    • 对于 `std::vector` 存储的原始指针,需要手动遍历并调用 `delete` 释放内存
        • 示例
    • 对于 `std::vector` 存储的智能指针,智能指针会自动管理对象的生命周期
    • enable_shared_from_this 和 shared_from_this
    • expired 的作用
    • `std::shared_ptr` :引用计数是线程安全的,但对对象的访问和修改需要额外的同步
    • `td::unique_ptr`:不是线程安全的,不能在多个线程之间共享
    • `std::weak_ptr` :调用 `lock` 方法是线程安全的,但对对象的访问和修改需要额外的同步

曾经的代码是这样写的

delete 用于释放通过 new 分配的单个对象。
delete[] 用于释放通过 new[] 分配的数组。

单个对象的分配和释放

int* p = new int;  // 分配单个int对象
delete p;         // 释放单个int对象

数组的分配和释放

int* arr = new int[10];  // 分配包含10个int的数组
delete[] arr;            // 释放数组

示例

#include <iostream>class MyClass {
public:MyClass() { std::cout << "MyClass created" << std::endl; }~MyClass() { std::cout << "MyClass destroyed" << std::endl; }
};int main() {// 分配和释放单个对象MyClass* obj = new MyClass;delete obj;// 分配和释放数组MyClass* arr = new MyClass[3];delete[] arr;return 0;
}

C++标准库提供了几种智能指针

  • std::unique_ptr :独占所有权智能指针(Unique Ownership Smart Pointer)

  • std::shared_ptr :共享所有权智能指针(Shared Ownership Smart Pointer)

  • std::weak_ptr :弱引用智能指针(Weak Reference Smart Pointer)

1. std::unique_ptr

std::unique_ptr 是一种独占所有权的智能指针,意味着同一时间只有一个 std::unique_ptr 拥有某个对象的所有权。它在离开作用域时会自动释放所管理的对象。
使用场景
独占所有权:当一个对象只能有一个所有者时,使用 std::unique_ptr
高效转移所有权:通过 std::move 可以高效地转移 std::unique_ptr 的所有权。
示例代码

#include <memory>
#include <iostream>class MyClass {
public:MyClass() { std::cout << "MyClass created" << std::endl; }~MyClass() { std::cout << "MyClass destroyed" << std::endl; }
};int main() {std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>();// std::unique_ptr<MyClass> ptr2 = ptr1; // 错误:无法复制std::unique_ptr<MyClass> ptr2 = std::move(ptr1); // 合法:转移所有权return 0;
}

2. std::shared_ptr

std::shared_ptr 是一种共享所有权的智能指针,意味着多个 std::shared_ptr 可以同时指向同一个对象。对象的生存期由引用计数(reference count)管理,当最后一个 std::shared_ptr 被销毁时,引用计数归零,对象也被销毁。
使用场景
共享所有权:当多个对象需要共享同一资源时,使用 std::shared_ptr
示例代码

#include <memory>
#include <iostream>class MyClass {
public:MyClass() { std::cout << "MyClass created" << std::endl; }~MyClass() { std::cout << "MyClass destroyed" << std::endl; }
};int main() {std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();std::shared_ptr<MyClass> ptr2 = ptr1; // 合法:共享所有权std::cout << "Reference count: " << ptr1.use_count() << std::endl; // 引用计数为2return 0;
}

3. std::weak_ptr

std::weak_ptr 是一种不控制对象生存期的智能指针,它指向一个由 std::shared_ptr 管理的对象,但不增加引用计数。std::weak_ptr 主要用于解决循环引用问题。
使用场景
弱引用:当需要访问一个由 std::shared_ptr 管理但不应该延长其生存期的对象时,使用 std::weak_ptr
防止循环引用:在某些数据结构(如双向链表、图)中使用,以避免循环引用导致的内存泄漏。
示例代码

#include <memory>
#include <iostream>class MyClass {
public:MyClass() { std::cout << "MyClass created" << std::endl; }~MyClass() { std::cout << "MyClass destroyed" << std::endl; }std::weak_ptr<MyClass> ptr; // 使用 weak_ptr 打破循环引用
};int main() {std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();std::shared_ptr<MyClass> ptr2 = std::make_shared<MyClass>();// 使用 lock() 方法来从 weak_ptr 获取 shared_ptrptr1->ptr = ptr2; // ptr1->ptr 仍然是 weak_ptr,但这里存储的是 ptr2 的 shared_ptrptr2->ptr = ptr1; // ptr2->ptr 也是 weak_ptr,但这里存储的是 ptr1 的 shared_ptr// 这里需要将 weak_ptr 转换成 shared_ptr,以便可以访问 MyClass 的成员if (auto lockedPtr1 = ptr1->ptr.lock()) {std::cout << "ptr1's ptr is locked and points to: " << lockedPtr1.get() << std::endl;} else {std::cout << "ptr1's ptr is expired." << std::endl;}if (auto lockedPtr2 = ptr2->ptr.lock()) {std::cout << "ptr2's ptr is locked and points to: " << lockedPtr2.get() << std::endl;} else {std::cout << "ptr2's ptr is expired." << std::endl;}return 0;
}

输出

MyClass created
MyClass created
ptr1's ptr is locked and points to: 000001E5FCA463B0
ptr2's ptr is locked and points to: 000001E5FCA467D0
MyClass destroyed
MyClass destroyed

循环引用 (写一个错误的例子和正确的例子)

循环引用(cyclic reference)发生在两个或多个对象互相持有对方的引用,导致这些对象无法被正确释放。这种情况通常会导致内存泄漏。使用 std::shared_ptr 管理对象时,容易出现循环引用问题。

循环引用示例 错误的例子

以下是一个简单的循环引用示例:

#include <iostream>
#include <memory>class B; // 前向声明class A {
public:std::shared_ptr<B> ptrB;~A() { std::cout << "A destroyed" << std::endl; }
};class B {
public:std::shared_ptr<A> ptrA;~B() { std::cout << "B destroyed" << std::endl; }
};int main() {std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->ptrB = b;b->ptrA = a;return 0; // 循环引用导致 A 和 B 不会被正确释放
}

创建了两个类 A 和 B,并且它们之间存在循环引用。A 类包含一个指向 B 类的 std::shared_ptr,而 B 类也包含一个指向 A 类的 std::shared_ptr。这会导致循环引用问题,使得 A 和 B 的实例都无法被正确销毁,因为它们互相保持着对彼此的引用。,ab 互相持有对方的 shared_ptr,导致引用计数永远不会归零,从而导致内存泄漏。

循环引用示例 正确的例子

解决循环引用:使用 std::weak_ptr``std::weak_ptr 是一种不增加引用计数的智能指针,可以打破循环引用。以下是修正后的示例:

#include <iostream>
#include <memory>class B;class A {
public:std::shared_ptr<B> ptrB;~A() { std::cout << "A destroyed" << std::endl; }
};class B {
public:std::weak_ptr<A> ptrA; // 使用 weak_ptr 打破循环引用~B() { std::cout << "B destroyed" << std::endl; }
};int main() {std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->ptrB = b;b->ptrA = a;return 0; // A 和 B 会被正确释放
}

输出

A destroyed
B destroyed

对于 std::vector 存储的原始指针,需要手动遍历并调用 delete 释放内存

如果 std::vector 存储的是通过 new 分配的原始指针,必须手动释放这些对象。可以通过循环遍历 vector 来删除所有指针指向的对象,然后清空 vector

示例
#include <vector>
#include <iostream>class MyClass {
public:MyClass() { std::cout << "MyClass created" << std::endl; }~MyClass() { std::cout << "MyClass destroyed" << std::endl; }
};int main() {std::vector<MyClass*> vec;vec.push_back(new MyClass());vec.push_back(new MyClass());// 释放内存for (MyClass* ptr : vec) {delete ptr;}vec.clear();return 0;
}

在这个例子中,手动遍历 vector,对每个指针调用 delete 来释放内存,然后清空 vectorstd::vector 中装智能指针

对于 std::vector 存储的智能指针,智能指针会自动管理对象的生命周期

如果 std::vector 存储的是智能指针(如 std::unique_ptrstd::shared_ptr),智能指针会自动管理对象的生命周期,当 vector 被销毁时,智能指针会自动释放所管理的对象,无需手动释放。

#include <vector>
#include <memory>
#include <iostream>class MyClass {
public:MyClass() { std::cout << "MyClass created" << std::endl; }~MyClass() { std::cout << "MyClass destroyed" << std::endl; }
};int main() {std::vector<std::unique_ptr<MyClass>> vec;vec.push_back(std::make_unique<MyClass>());vec.push_back(std::make_unique<MyClass>());// 当 vec 被销毁时,存储的 unique_ptr 会自动释放 MyClass 对象return 0;
}

输出

MyClass created
MyClass created
MyClass destroyed
MyClass destroyed

在这个例子中,std::unique_ptr 会在 vector 作用域结束时自动释放内存。使用 std::shared_ptr

#include <vector>
#include <memory>
#include <iostream>class MyClass {
public:MyClass() { std::cout << "MyClass created" << std::endl; }~MyClass() { std::cout << "MyClass destroyed" << std::endl; }
};int main() {std::vector<std::shared_ptr<MyClass>> vec;vec.push_back(std::make_shared<MyClass>());vec.push_back(std::make_shared<MyClass>());// 当 vec 被销毁时,存储的 shared_ptr 会自动管理 MyClass 对象的生命周期return 0;
}

输出

MyClass created
MyClass created
MyClass destroyed
MyClass destroyed

在这个例子中,std::shared_ptr 会在 vector 作用域结束时自动释放内存,只要没有其他 shared_ptr 仍然指向相同的对象。

enable_shared_from_this 和 shared_from_this

enable_shared_from_this 和 shared_from_this 提供了一种安全生成 shared_ptr 的方式
std::enable_shared_from_this 是一个帮助类,允许对象安全地生成指向自身的 std::shared_ptr。
安全地创建 shared_ptr:通过 enable_shared_from_this,可以确保在类成员函数中安全地生成指向当前对象的 shared_ptr,避免多次创建不同的 shared_ptr 导致的问题。

#include <iostream>
#include <memory>class MyClass : public std::enable_shared_from_this<MyClass> {
public:std::shared_ptr<MyClass> getSharedPtr() {return shared_from_this();}~MyClass() { std::cout << "MyClass destroyed" << std::endl; }
};int main() {std::shared_ptr<MyClass> obj = std::make_shared<MyClass>();std::shared_ptr<MyClass> obj2 = obj->getSharedPtr();std::cout << "obj use count: " << obj.use_count() << std::endl;  // 输出 2std::cout << "obj2 use count: " << obj2.use_count() << std::endl; // 输出 2return 0;
}

输出

obj use count: 2
obj2 use count: 2
MyClass destroyed

expired 的作用

expired 是 std::weak_ptr 类中的一个成员函数,用来检查 weak_ptr 指向的对象是否已经被销毁。std::weak_ptr 是一种不拥有对象的指针,它可以观察一个由 std::shared_ptr 管理的对象,但不会影响对象的生命周期。
expired 返回一个布尔值,如果 std::weak_ptr 指向的对象已经被销毁(引用计数为零),它返回 true;否则返回 false。

使用 expired 检查对象是否仍然存在
使用 expired 可以检查 std::weak_ptr 指向的对象是否仍然存在。如果对象已经被销毁,可以避免对一个无效的对象进行操作。

示例代码
以下是一个使用 std::weak_ptr 和 expired 的示例,展示了如何避免循环引用和检查对象的有效性

#include <iostream>
#include <memory>class MyClass {
public:MyClass() { std::cout << "MyClass created" << std::endl; }~MyClass() { std::cout << "MyClass destroyed" << std::endl; }std::weak_ptr<MyClass> ptr; // 使用 weak_ptr 打破循环引用
};int main() {{// 创建两个 shared_ptrauto ptr1 = std::make_shared<MyClass>();auto ptr2 = std::make_shared<MyClass>();// 互相引用对方的 shared_ptrptr1->ptr = ptr2;ptr2->ptr = ptr1;// 检查 ptr1 和 ptr2 指向的对象是否仍然存在if (ptr1->ptr.expired()) {std::cout << "ptr1's ptr is expired." << std::endl;} else {std::cout << "ptr1's ptr is still valid." << std::endl;}if (ptr2->ptr.expired()) {std::cout << "ptr2's ptr is expired." << std::endl;} else {std::cout << "ptr2's ptr is still valid." << std::endl;}} // 这里 ptr1 和 ptr2 超出作用域,被销毁// 此时 ptr1 和 ptr2 所指向的对象已经被销毁// 所有 MyClass 对象都已经被销毁,析构函数已经被调用return 0;
}

std::shared_ptr :引用计数是线程安全的,但对对象的访问和修改需要额外的同步

当需要多个线程共享对象的所有权,确保对象在有线程使用时不会被销毁,不存在循环引用的风险,多线程里可以用std::shared_ptr。

例子

#include <iostream>
#include <memory>
#include <thread>
#include <vector>void threadFunction(std::shared_ptr<int> sharedPtr) {std::this_thread::sleep_for(std::chrono::milliseconds(100));std::cout << "Value: " << *sharedPtr << std::endl;
}int main() {std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);std::vector<std::thread> threads;for (int i = 0; i < 10; ++i) {threads.push_back(std::thread(threadFunction, sharedPtr));}for (auto& t : threads) {t.join();}return 0;
}

td::unique_ptr:不是线程安全的,不能在多个线程之间共享

std::unique_ptr 不是线程安全的。因为 std::unique_ptr 不能被复制,只能被移动,这意味着同一时间只能有一个线程拥有该指针的所有权。如果需要在多个线程中共享所有权,应该使用 std::shared_ptr。

std::weak_ptr :调用 lock 方法是线程安全的,但对对象的访问和修改需要额外的同步

当需要避免循环引用,线程只需要观察对象的生命周期,而不需要拥有对象,需要临时访问对象,而不需要延长对象的生命周期,多线程中用std::weak_ptr。

#include <iostream>
#include <memory>
#include <thread>
#include <vector>void threadFunction(std::weak_ptr<int> weakPtr) {std::this_thread::sleep_for(std::chrono::milliseconds(100));if (auto sharedPtr = weakPtr.lock()) {std::cout << "Value: " << *sharedPtr << std::endl;} else {std::cout << "Object has been destroyed." << std::endl;}
}int main() {std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);std::weak_ptr<int> weakPtr = sharedPtr;std::vector<std::thread> threads;for (int i = 0; i < 10; ++i) {threads.push_back(std::thread(threadFunction, weakPtr));}for (auto& t : threads) {t.join();}sharedPtr.reset(); // 销毁对象return 0;
}

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

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

相关文章

C语言 | Leetcode C语言题解之第283题移动零

题目&#xff1a; 题解&#xff1a; void swap(int *a, int *b) {int t *a;*a *b, *b t; }void moveZeroes(int *nums, int numsSize) {int left 0, right 0;while (right < numsSize) {if (nums[right]) {swap(nums left, nums right);left;}right;} }

Layui表格合并、表格折叠树

1、核心代码&#xff1a; let tableMerge layui.tableMerge; // 引入合并的插件&#xff0c;插件源文件在最后let tableData [{pid: 0,cid: 111,sortNum: 1, // 序号pName: 数据父元素1,name: 数据1,val: 20,open: true, // 子树是否展开hasChild: true, // 有子数据opt: 数据…

代码随想录训练第三十天|01背包理论基础、01背包、LeetCode416.分割等和子集

文章目录 01背包理论基础01背包二维dp数组01背包一维dp数组(滚动数组) 416.分割等和子集思路 01背包理论基础 背包问题的理论基础重中之重是01背包&#xff0c;一定要理解透&#xff01; leetcode上没有纯01背包的问题&#xff0c;都是01背包应用方面的题目&#xff0c;也就是…

Pytorch深度学习实践(8)多分类任务

多分类问题 多分类问题主要是利用了Softmax分类器&#xff0c;数据集采用MNIST手写数据集 设计方法&#xff1a; 把每一个类别看成一个二分类的问题&#xff0c;分别输出10个概率 但是这种方法存在一种问题&#xff1a;不存在抑制问题&#xff0c;即按照常规来讲&#xff0c…

stm32h7串口发送寄存器空中断

关于stm32串口的发送完成中断UART_IT_TC网上资料挺多的&#xff0c;但是使用发送寄存器空中断UART_IT_TXE的不太多 UART_IT_TC 和 UART_IT_TXE区别 UART_IT_TC 和 UART_IT_TXE 是两种不同的 UART 中断源&#xff0c;用于表示不同的发送状态。它们的主要区别如下&#xff1a; …

raise JSONDecodeError(“Expecting value”, s, err.value) from None

raise JSONDecodeError(“Expecting value”, s, err.value) from None 目录 raise JSONDecodeError(“Expecting value”, s, err.value) from None 【常见模块错误】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是…

数字图像处理笔记(三) ---- 傅里叶变换的基本原理

系列文章目录 数字图像处理笔记&#xff08;一&#xff09;---- 图像数字化与显示 数字图像处理笔记&#xff08;二&#xff09;---- 像素加图像统计特征 数字图像处理笔记&#xff08;三) ---- 傅里叶变换的基本原理 文章目录 系列文章目录前言一、傅里叶变换二、离散傅里叶变…

ChatTTS(文本转语音) 一键本地安装爆火语音模型

想不想让你喜欢的文章&#xff0c;有着一个动听的配音&#xff0c;没错&#xff0c;他就可以实现。 ChatTTS 是一款专为对话场景设计的文本转语音模型&#xff0c;例如 LLM 助手对话任务。它支持英语和中文两种语言。 当下爆火模型&#xff0c;在Git收获23.5k的Star&#xff…

【Pod 详解】Pod 的概念、使用方法、容器类型

《Pod 详解》系列&#xff0c;共包含以下几篇文章&#xff1a; Pod 的概念、使用方法、容器类型Pod 的生命周期&#xff08;一&#xff09;&#xff1a;Pod 阶段与状况、容器的状态与重启策略Pod 的生命周期&#xff08;二&#xff09;&#xff1a;Pod 的健康检查之容器探针Po…

C++入门基础:C++中的常用操作符练习

开头介绍下C语言先&#xff0c;C是一种广泛使用的计算机程序设计语言&#xff0c;起源于20世纪80年代&#xff0c;由比雅尼斯特劳斯特鲁普在贝尔实验室开发。它是C语言的扩展&#xff0c;增加了面向对象编程的特性。C的应用场景广泛&#xff0c;包括系统软件、游戏开发、嵌入式…

智慧医院临床检验管理系统源码(LIS),全套LIS系统源码交付,商业源码,自主版权,支持二次开发

实验室信息系统是集申请、采样、核收、计费、检验、审核、发布、质控、查询、耗材控制等检验科工作为一体的网络管理系统。它的开发和应用将加快检验科管理的统一化、网络化、标准化的进程。一体化设计&#xff0c;与其他系统无缝连接&#xff0c;全程化条码管理。支持危机值管…

DataX(二):DataX安装与入门

1. 官方地址 下载地址&#xff1a;http://datax-opensource.oss-cn-hangzhou.aliyuncs.com/datax.tar.gz 源码地址&#xff1a;GitHub - alibaba/DataX: DataX是阿里云DataWorks数据集成的开源版本。 2. 前置要求 Linux JDK(1.8 以上&#xff0c;推荐 1.8) Python(推荐 Pyt…

一文总结代理:代理模式、代理服务器

概述 代理在计算机编程领域&#xff0c;是一个很通用的概念&#xff0c;包括&#xff1a;代理设计模式&#xff0c;代理服务器等。 代理类持有具体实现类的实例&#xff0c;将在代理类上的操作转化为实例上方法的调用。为某个对象提供一个代理&#xff0c;以控制对这个对象的…

测试分类篇

按测试对象划分 这里可以分为界面测试, 可靠性测试, 容错率测试, 文档测试, 兼容性测试, 安装卸载测试, 安全测试, 性能测试, 内存泄露测试. 界面测试 界面测试&#xff08;简称UI测试)&#xff0c;指按照界面的需求&#xff08;一般是UI设计稿&#xff09;和界面的设计规则…

DOS攻击实验

实验背景 Dos 攻击是指故意的攻击网络协议实现的缺陷或直接通过野蛮手段&#xff0c;残忍地耗尽被攻击对象的资源&#xff0c;目的是让目标计算机或网络无法提供正常的服务或资源访问&#xff0c;使目标系统服务系统停止响应甚至崩溃。 实验设备 一个网络 net:cloud0 一台模…

基于微信小程序+SpringBoot+Vue的儿童预防接种预约系统(带1w+文档)

基于微信小程序SpringBootVue的儿童预防接种预约系统(带1w文档) 基于微信小程序SpringBootVue的儿童预防接种预约系统(带1w文档) 开发合适的儿童预防接种预约微信小程序&#xff0c;可以方便管理人员对儿童预防接种预约微信小程序的管理&#xff0c;提高信息管理工作效率及查询…

24暑假算法刷题 | Day22 | LeetCode 77. 组合,216. 组合总和 III,17. 电话号码的字母组合

目录 77. 组合题目描述题解 216. 组合总和 III题目描述题解 17. 电话号码的字母组合题目描述题解 77. 组合 点此跳转题目链接 题目描述 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1&#xff1a; 输…

移动UI:排行榜单页面如何设计,从这五点入手,附示例。

移动UI的排行榜单页面设计需要考虑以下几个方面&#xff1a; 1. 页面布局&#xff1a; 排行榜单页面的布局应该清晰明了&#xff0c;可以采用列表的形式展示排行榜内容&#xff0c;同时考虑到移动设备的屏幕大小&#xff0c;应该设计合理的滚动和分页机制&#xff0c;确保用户…

贪心算法.

哈夫曼树 哈夫曼树&#xff08;Huffman Tree&#xff09;&#xff0c;又称为霍夫曼树或最优二叉树&#xff0c;是一种带权路径长度最短的二叉树&#xff0c;常用于数据压缩。 定义&#xff1a;给定N个权值作为N个叶子结点&#xff0c;构造一棵二叉树&#xff0c;若该树…

普乐蛙VR航天航空体验馆知识走廊VR体验带你登陆月球

VR航天航空设备是近年来随着虚拟现实&#xff08;VR&#xff09;技术的快速发展而兴起的一种新型设备&#xff0c;它结合了航天航空领域的专业知识与VR技术的沉浸式体验&#xff0c;为用户提供了前所未有的航天航空体验。以下是对VR航天航空设备的详细介绍&#xff1a; 一、设备…