[C++11] 智能指针(auto_ptr(弃用)、unique_ptr、shared_ptr、weak_ptr)详细解读

说明:本文主要解释auto_ptr、unique_ptr、shared_ptr、weak_ptr这几种智能指针。接下来我们对每一个指针类型进行详细说明并给出一些基本的使用方式,重在深入理解。

在 C++ 中,auto_ptr、unique_ptr、shared_ptr 和 weak_ptr四种智能指针主要用于管理动态分配的对象的生命周期。它们在所有权管理和内存安全方面有所不同。

1 auto_ptr(已废弃,需了解)

auto_ptr 是 C++98 中引入的智能指针,但在 C++11 中被标记为已废弃。它实现了独占式所有权,即一个 auto_ptr 对象拥有对动态分配对象的唯一所有权。当 auto_ptr 被销毁时,它会自动释放所拥有的对象。然而,auto_ptr 存在一些严重的问题,如无法正确处理数组和容器等情况,因此不推荐使用。但是关于auto_ptr的使用,我们还是需要了解。

以下是一个较为完整的示例,演示了 auto_ptr 的基本操作:

#include <iostream>
#include <memory>class MyClass {
public:MyClass(int data) : data(data) {std::cout << "MyClass object created with data: " << data << std::endl;}~MyClass() {std::cout << "MyClass object destroyed with data: " << data << std::endl;}void printData() {std::cout << "Data: " << data << std::endl;}private:int data;
};int main() {// 创建 auto_ptr,使用 new 运算符std::auto_ptr<MyClass> myClassPtr(new MyClass(42));// 使用箭头运算符访问对象成员函数myClassPtr->printData();// 使用 * 运算符解引用 auto_ptr,并访问对象成员函数(*myClassPtr).printData();// 转移所有权到另一个 auto_ptrstd::auto_ptr<MyClass> myClassPtr2 = myClassPtr;// 使用箭头运算符访问对象成员函数myClassPtr2->printData();// 判断 auto_ptr 是否为空if (myClassPtr) {std::cout << "myClassPtr is not empty." << std::endl;} else {std::cout << "myClassPtr is empty." << std::endl;}// 重置 auto_ptr,解除与资源的关联myClassPtr.reset();// 判断 auto_ptr 是否为空if (myClassPtr2) {std::cout << "myClassPtr2 is not empty." << std::endl;} else {std::cout << "myClassPtr2 is empty." << std::endl;}return 0;
}

这里需要注意:

  • auto_ptr 在所有权转移后会导致原来的指针为空,因此在使用它时需要格外小心。
  • auto_ptr 在容器等场景中的行为也可能不符合预期。因此,建议使用更安全、功能更强大的 unique_ptr 或其他智能指针替代 auto_ptr。

2 unique_ptr

unique_ptr 是 C++11 引入的智能指针,取代了 auto_ptr。它也实现了独占式所有权,但是提供了更严格的所有权管理和更好的性能。unique_ptr 不能被复制,只能通过移动语义来转移所有权。它使用了 RAII(资源获取即初始化)的原则,确保在作用域结束时自动释放所拥有的对象。unique_ptr 还提供了自定义删除器的能力,以便释放非默认方式分配的资源。

下面是一个使用 unique_ptr 的较为完整的示例,涵盖了常见的基本操作:

#include <iostream>
#include <memory>class MyClass {
public:MyClass(int data) : data(data) {std::cout << "MyClass object created with data: " << data << std::endl;}~MyClass() {std::cout << "MyClass object destroyed with data: " << data << std::endl;}void printData() {std::cout << "Data: " << data << std::endl;}private:int data;
};int main() {// 创建 unique_ptr,使用 new 运算符std::unique_ptr<MyClass> myClassPtr(new MyClass(42));// 使用箭头运算符访问对象成员函数myClassPtr->printData();// 使用 * 运算符解引用 unique_ptr,并访问对象成员函数(*myClassPtr).printData();// 判断 unique_ptr 是否为空if (myClassPtr) {std::cout << "myClassPtr is not empty." << std::endl;} else {std::cout << "myClassPtr is empty." << std::endl;}// 重置 unique_ptr,解除与资源的关联myClassPtr.reset();// 判断 unique_ptr 是否为空if (myClassPtr) {std::cout << "myClassPtr is not empty." << std::endl;} else {std::cout << "myClassPtr is empty." << std::endl;}return 0;
}

这个示例演示了 unique_ptr 的常见用法,包括创建和初始化、访问对象成员函数、重置和判断是否为空。通过使用 unique_ptr,我们可以自动管理动态分配的对象,并确保只有一个 unique_ptr 拥有对资源的所有权,避免资源的共享和所有权冲突的问题。

3 shared_ptr

shared_ptr 是一种共享所有权的智能指针。它可以跨多个 shared_ptr 对象共享对同一动态分配对象的所有权。shared_ptr 内部使用引用计数来跟踪对象的引用数,并在引用数为零时自动释放所拥有的对象。shared_ptr 具有拷贝构造函数和拷贝赋值运算符,允许多个 shared_ptr 共享同一对象。当最后一个 shared_ptr 被销毁时,它会自动释放对象。然而,shared_ptr 有可能产生循环引用,导致内存泄漏。为了解决这个问题,C++11 引入了 weak_ptr。

下面是一个使用 shared_ptr 的较为完整的示例,涵盖了常见的基本操作:

#include <iostream>
#include <memory>class MyClass {
public:MyClass(int data) : data(data) {std::cout << "MyClass object created with data: " << data << std::endl;}~MyClass() {std::cout << "MyClass object destroyed with data: " << data << std::endl;}void printData() {std::cout << "Data: " << data << std::endl;}private:int data;
};int main() {// 创建 shared_ptr,使用 make_shared 函数std::shared_ptr<MyClass> myClassPtr = std::make_shared<MyClass>(42);// 使用箭头运算符访问对象成员函数myClassPtr->printData();// 使用 * 运算符解引用 shared_ptr,并访问对象成员函数(*myClassPtr).printData();// 获取 shared_ptr 的引用计数std::cout << "Reference count: " << myClassPtr.use_count() << std::endl;// 创建新的 shared_ptr,与已有的 shared_ptr 共享资源std::shared_ptr<MyClass> myClassPtr2 = myClassPtr;// 获取共享资源的引用计数std::cout << "Reference count: " << myClassPtr.use_count() << std::endl;// 重置 shared_ptr,解除与资源的关联myClassPtr.reset();// 获取共享资源的引用计数std::cout << "Reference count: " << myClassPtr2.use_count() << std::endl;// 判断 shared_ptr 是否为空if (myClassPtr) {std::cout << "myClassPtr is not empty." << std::endl;} else {std::cout << "myClassPtr is empty." << std::endl;}return 0;
}

这个示例演示了 shared_ptr 的常见用法,包括创建和初始化、访问对象成员函数、引用计数操作、重置和判断是否为空。通过使用 shared_ptr,我们可以自动管理动态分配的对象,避免手动释放资源和内存泄漏的问题。

4 weak_ptr

weak_ptr 是 C++ 中用于解决循环引用和避免悬空指针的智能指针类型。它通常与 shared_ptr 一起使用,用于监测对象的生命周期而不增加引用计数。 weak_ptr 的常用用法如下:

  • 解决循环引用:当两个或多个对象互相持有对方的 shared_ptr,形成循环引用时,可以使用 weak_ptr 来打破循环引用并释放资源。
  • 检测对象是否存在:通过调用 expired() 函数,可以检查 weak_ptr 是否指向有效的对象。如果对象已被销毁,expired() 返回 true,否则返回 false。
  • 获取 shared_ptr:可以通过调用 lock() 函数获取指向 weak_ptr 所管理对象的 shared_ptr。如果对象存在,则返回有效的 shared_ptr;如果对象已被销毁,返回空的 shared_ptr。

4.1 weak_ptr 的常用用法

weak_ptr 的常用用法代码实现如下:

#include <iostream>
#include <memory>class MyClass {
public:~MyClass() {std::cout << "MyClass object destroyed." << std::endl;}
};int main() {std::weak_ptr<MyClass> weakPtr;{std::shared_ptr<MyClass> sharedPtr = std::make_shared<MyClass>();weakPtr = sharedPtr;if (weakPtr.expired()) {std::cout << "weakPtr is expired." << std::endl;} else {std::cout << "weakPtr is not expired." << std::endl;std::shared_ptr<MyClass> lockedPtr = weakPtr.lock();if (lockedPtr) {std::cout << "Weak pointer locked successfully." << std::endl;// 在这里可以使用 lockedPtr 操作对象} else {std::cout << "Weak pointer failed to lock." << std::endl;}}}if (weakPtr.expired()) {std::cout << "weakPtr is expired." << std::endl;} else {std::cout << "weakPtr is not expired." << std::endl;std::shared_ptr<MyClass> lockedPtr = weakPtr.lock();if (lockedPtr) {std::cout << "Weak pointer locked successfully." << std::endl;// 在这里可以使用 lockedPtr 操作对象} else {std::cout << "Weak pointer failed to lock." << std::endl;}}return 0;
}

在这个示例中,我们使用了 expired() 和 lock() 函数来检测和操作 weak_ptr。

通过这个示例,我们展示了如何结合使用 expired() 函数和 lock() 函数来检测 weak_ptr 是否过期,并使用安全的 shared_ptr 进行对象操作。

4.2 循环引用问题

我们从前面的shared_ptr中知道,当使用 shared_ptr 时,每个 shared_ptr 对象内部都有一个引用计数(reference count),用于跟踪对象的引用数。引用计数表示有多少个 shared_ptr 共享对同一对象的所有权。当一个 shared_ptr 对象被复制或拷贝构造时,其引用计数会增加。当 shared_ptr 对象被销毁(超出作用域、被重新赋值或显式调用 reset())时,其引用计数会减少。当引用计数为零时,表示没有 shared_ptr 对象拥有对该对象的所有权,此时会释放对象的内存。

那么什么是循环引用计数呢?

循环引用指的是两个或多个对象彼此持有对方的 shared_ptr,导致它们的引用计数永远无法减至零,从而造成内存泄漏。这是因为即使没有其他指针引用这些对象,它们之间的循环引用仍使引用计数保持非零值,对象无法被销毁。,一个具体的代码案例如下所示:

#include <iostream>
#include <memory>class MyClassB;  // 前向声明class MyClassA {
public:std::shared_ptr<MyClassB> bPtr;MyClassA() {std::cout << "MyClassA constructed." << std::endl;}~MyClassA() {std::cout << "MyClassA destroyed." << std::endl;}
};class MyClassB {
public:std::shared_ptr<MyClassA> aPtr;MyClassB() {std::cout << "MyClassB constructed." << std::endl;}~MyClassB() {std::cout << "MyClassB destroyed." << std::endl;}
};int main() {std::shared_ptr<MyClassA> aPtr = std::make_shared<MyClassA>();std::shared_ptr<MyClassB> bPtr = std::make_shared<MyClassB>();// 形成循环引用aPtr->bPtr = bPtr;bPtr->aPtr = aPtr;return 0;
}

为了解决这个问题,C++11 引入了 weak_ptr。解决引用计数问题可以这样做:

#include <iostream>
#include <memory>class MyClassB;class MyClassA {
public:std::shared_ptr<MyClassB> bPtr;MyClassA() {std::cout << "MyClassA constructed." << std::endl;}~MyClassA() {std::cout << "MyClassA destroyed." << std::endl;}
};class MyClassB {
public:std::weak_ptr<MyClassA> aPtr;MyClassB() {std::cout << "MyClassB constructed." << std::endl;}~MyClassB() {std::cout << "MyClassB destroyed." << std::endl;}
};int main() {std::shared_ptr<MyClassA> aPtr = std::make_shared<MyClassA>();std::shared_ptr<MyClassB> bPtr = std::make_shared<MyClassB>();aPtr->bPtr = bPtr;bPtr->aPtr = aPtr;return 0;
}

在这个修改后的示例中,MyClassB 的成员变量 aPtr 由 shared_ptr 改为 weak_ptr。这样,MyClassB 对象持有的是MyClassA 对象的弱引用,不会增加引用计数,也不会阻止 MyClassA 对象的销毁。当程序结束时,引用计数会降至零,两个对象的析构函数会被正确调用,内存得到正确释放,避免了内存泄漏。也就是使用 weak_ptr 可以打破循环引用,避免内存泄漏的发生。

但是如果 MyClassA 和 MyClassB 分别定义了一个 shared_ptr 成员变量,用于持有对方的 shared_ptr,并且不允许更改类中引用类型,那么直接使用 weak_ptr 是无法解决循环引用的问题的。在这种情况下,可以考虑引入一个第三方管理对象,该对象负责监控 MyClassA 和 MyClassB 对象的生命周期,并在适当的时候释放它们。

使用第三方管理对象来解决循环引用的问题,具体实现如下:

#include <iostream>
#include <memory>class MyClassA;
class MyClassB;class ObjectManager {
private:std::weak_ptr<MyClassA> aPtr;std::weak_ptr<MyClassB> bPtr;public:void setObjects(const std::shared_ptr<MyClassA>& a, const std::shared_ptr<MyClassB>& b) {aPtr = a;bPtr = b;}void checkAndReleaseObjects() {if (aPtr.expired() && bPtr.expired()) {std::cout << "Both objects expired. Releasing resources." << std::endl;// 执行释放资源的操作}}
};class MyClassA {
public:std::shared_ptr<MyClassB> bPtr;MyClassA() {std::cout << "MyClassA constructed." << std::endl;}~MyClassA() {std::cout << "MyClassA destroyed." << std::endl;}
};class MyClassB {
public:std::shared_ptr<MyClassA> aPtr;MyClassB() {std::cout << "MyClassB constructed." << std::endl;}~MyClassB() {std::cout << "MyClassB destroyed." << std::endl;}
};int main() {std::shared_ptr<MyClassA> aPtr = std::make_shared<MyClassA>();std::shared_ptr<MyClassB> bPtr = std::make_shared<MyClassB>();ObjectManager manager;manager.setObjects(aPtr, bPtr);// 形成循环引用aPtr->bPtr = bPtr;bPtr->aPtr = aPtr;// 在适当的时候检查并释放对象manager.checkAndReleaseObjects();return 0;
}

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

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

相关文章

拌合楼管理系统(八) c#海康威视摄像头车牌识别

前言: c#调用海康威视SDK实现车牌识别 原本以为海康威视sdk的Demo里面没有车牌识别的实例,后来发现自己肤浅了,官方是有提供的,只是车牌识别是通过安防布警的方式实现的.程序主动监听,触发告警后获取到车牌信息. 一、接口调用的流程&#xff1a; 首先初始化sdk -> 开…

SQLiteC/C++接口详细介绍sqlite3_stmt类(四)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍sqlite3_stmt类&#xff08;三&#xff09; 下一篇&#xff1a;SQLiteC/C接口详细介绍sqlite3_stmt类&#xff08;五&#xff09; 7. sqlite3_bind_parameter_count函数 sqlite3_bind_param…

流畅的 Python 第二版(GPT 重译)(十一)

第二十章&#xff1a;并发执行器 抨击线程的人通常是系统程序员&#xff0c;他们心中有着典型应用程序员终其一生都不会遇到的用例。[…] 在 99%的用例中&#xff0c;应用程序员可能会遇到的情况是&#xff0c;生成一堆独立线程并将结果收集到队列中的简单模式就是他们需要了解…

Java newInstance方法学习

用newInstance与用new是有区别的&#xff0c;区别在于创建对象的方式不一样&#xff0c;前者是使用类加载机制&#xff1b; newInstance方法要求该 Class 对应类有无参构造方法&#xff1b; 执行 newInstance()方法实际上就是使用对应类的无参构造方法来创建该类的实例&#x…

docker离线安装并修改存储目录

docker下载 根据cpu选择不同版本&#xff0c;正常x86就选x86_64 下载地址&#xff1a;https://download.docker.com/linux/static/stable/ docker安装 tar -zxvf arm-docker-25.0.4.tgz sudo cp docker/* /usr/bin/ rm -rf docker/* mkdir /etc/docker vi /etc/docker/daemo…

YOLOV4-车道线检测-车距离预测

1.前言 最近在看华为的CANN框架&#xff0c;发现了一些很有意思的开源算法(本文所有的代码都出自华为开源git发布的代码)&#xff0c;华为最近出了AI PRO开发板&#xff0c;想着现在开发板上用用(不想重新配置环境了&#xff0c;麻烦还累)&#xff0c;看着代码有onnx的模型&…

浅学redis

一、持久化 1.为什么需要持久化&#xff1f; 如果不将内存中的数据保存到磁盘&#xff0c;那么一旦服务器进程退出&#xff0c;服务器中数据也会消失&#xff0c;所以redis提供了持久化功能 2.RDB&#xff08;redis database&#xff09; redis配置中&#xff0c;默认使用RDB…

绝地求生:PUBG延长GPU崩溃时间新方法

相信大家都在被GPU游戏崩溃苦恼已久&#xff0c;PUBG这个游戏崩溃&#xff0c;跟超频是没有多大关系的&#xff0c;只要超频TM5过测&#xff0c;YC过测&#xff0c;或者双烤过测&#xff0c;就没问题。主要是这个游戏的优化不行&#xff0c;特别40系显卡&#xff0c;对内存条也…

C# System.Console.WriteLine的格式化输出

C#中Console.WriteLine()函数输出格式详解 真 C#中Console.WriteLine()函数输出格式详解 假 using System;namespace Test {class TODO {static void Main() {System.Console.WriteLine("!{1,4:D3}!", 12, 24);}} }! 024!{index[,alignment][:formatString]} 其中&a…

免费AI出图神器:StableStudio——定义AI作画新前景

StableStudio&#xff1a;探索艺术与科技的无限交界&#xff0c;StableStudio引领AI智能创作新浪潮&#xff01; - 精选真开源&#xff0c;释放新价值。 概览 ChatGPT大语言模型AI的诞生引爆了对AIGC的讨论。AIGC 又称生成式 AI (Generative AI)&#xff0c;是继专业生产内容&…

VSCode使用MSVC编译器

1.其他大佬的配置&#xff1a;下载和安装库环境 2.安装好C/C插件<选择1.8.4的版本最好>。 3.分别生成对应的默认模板即可。但是其中参数可能需要配置。 task.json {"version": "2.0.0","tasks": [{"type": "cppbuild"…

Python 全栈体系【四阶】(十七)

第五章 深度学习 一、基本理论 3. 深度神经网络训练法则 3.1 损失函数 3.1.1 什么是损失函数&#xff1f; 损失函数&#xff08;Loss Function&#xff09;&#xff0c;也有称之为代价函数&#xff08;Cost Function&#xff09;&#xff0c;用来度量预测值和实际值之间的差…

html5cssjs代码 029 CSS计数器

html5&css&js代码 029 CSS计数器 一、代码二、解释 该HTML代码定义了一个网页的结构和样式。在头部&#xff0c;通过CSS样式定义了body和h1-h2元素的样式。body元素的样式包括文本居中、计数器重置、字体颜色和背景颜色。h2元素的样式使用了CSS计数器来自动在标题前添加…

2024最新华为OD机试试题库全 -【转盘寿司】- C卷

1. 🌈题目详情 1.1 ⚠️题目 寿司店周年庆,正在举办优惠活动回馈新老客户。 寿司转盘上总共有 n 盘寿司,prices[i] 是第 i 盘寿司的价格, 如果客户选择了第 i 盘寿司,寿司店免费赠送客户距离第 i 盘寿司最近的下一盘寿司 j,前提是 prices[j] < prices[i],如果没…

RIDE控制台中文显示为乱码问题解决方案【版本1.7.4.1】

1、方法&#xff1a; 将 C:\Users\user_name\AppData\Roaming\Python\Python37\site-packages\robotide\contrib\testrunner\testrunnerplugin.py文件中的第80行修改&#xff0c;改为utf-8 2、修改代码位置&#xff1a; 3、效果&#xff1a; 4、参考文章 试了前面的方法没有…

千益畅行共享旅游卡怎么代理? 开启江浙沪区域畅享0费用旅游新机遇

想要探索副业项目的新领域&#xff0c;共享旅游卡无疑是一个极具潜力的选择。千益畅行旅游卡作为市场上的佼佼者&#xff0c;为众多创业者提供了一个零风险、高回报的副业项目。无论是上海共享旅游卡代理、江苏共享旅游卡代理、还是浙江共享旅游卡代理&#xff0c;都能让你轻松…

vue-pdf的注意事项

vue2项目 node版本切换到14.21.3 npm install --save vue-pdf4.2.0 npm install pdfjs-dist2.5.207 [^ 注意]&#xff1a;一般情况下&#xff0c;执行上述命令就可以解决这个问题&#xff0c;但有时候在执行完后还是不行&#xff0c;这时候如果你执意要搜索为什么&#xff0c;…

Python 深度学习第二版(GPT 重译)(一)

前言 序言 如果你拿起这本书&#xff0c;你可能已经意识到深度学习在最近对人工智能领域所代表的非凡进步。我们从几乎无法使用的计算机视觉和自然语言处理发展到了在你每天使用的产品中大规模部署的高性能系统。这一突然进步的后果几乎影响到了每一个行业。我们已经将深度学…

【C语言】结构体内存对齐问题

1.结构体内存对齐 我们已经基本掌握了结构体的使用了。那我们现在必须得知道结构体在内存中是如何存储的&#xff1f;内存是如何分配的&#xff1f;所以我们得知道如何计算结构体的大小&#xff1f;这就引出了我们今天所要探讨的内容&#xff1a;结构体内存对齐。 1.1 对齐规…

【Redis】Redis常见原理和数据结构

Redis 什么是redis redis是一款基于内存的k-v数据结构的非关系型数据库&#xff0c;读写速度非常快&#xff0c;常用于缓存&#xff0c;消息队列、分布式锁等场景。 redis的数据类型 string&#xff1a;字符串 缓存对象&#xff0c;分布式ID&#xff0c;token&#xff0c;se…