【Example】C++ 标准库智能指针 unique_ptr 与 shared_ptr

简介: 在现代 C + + 编程中,标准库包含智能指针,智能指针可处理对其拥有的内存的分配和删除,这些指针用于帮助确保程序不会出现内存和资源泄漏,并具有异常安全。C 样式编程的一个主要 bug 类型是内存泄漏。 泄漏通常是由于为分配的内存的调用失败引起的 delete new。 现代 C++ 强调“资源获取即初始化”(RAII) 原则。 其理念很简单。 资源(堆内存、文件句柄、套接字等)应由对象“拥有”。 该对象在其构造函数中创建或接收新分配的资源,并在其析构函数中将此资源删除。 RAII 原则可确保当所属对象超出范围时,所有资源都能正确返回到操作系统。--Microsoft Docs

为了支持对 RAII 原则的简单采用,C++ 标准库提供了三种智能指针类型:

std::unique_ptr

std::shared_ptr

std::weak_ptr

 

====================================

unique_ptr

unique_ptr 类型智能指针在设计上最显著的特点是内部托管的指针一旦被创建就不能被任何形式的复制给另一个unique_ptr,只可以被移动给另一个unique_ptr。unique_ptr 没有拷贝构造函数,因此不能用于赋值。该指针最常用的情况是单例模式和编译防火墙的封装。

如果非要抬杠,使用 get() 函数获取到裸指针给另外一个裸指针,那么你使用智能指针的意义又何在呢?

任何智能指针都不应该去 get 裸指针使用,更不能 delete!

 

// 演示创建 unique_ptrunique_ptr<Brain>u_brain=make_unique<Brain>();
u_brain->HelloWorld();
// 移动 unique_ptrunique_ptr<Brain>um_barin=std::move(u_brain);
um_barin->HelloWorld();
// 移动方法2std::swap(u_brain, um_brain);
// 错误// um_barin = u_brain;// u_brain->HelloWorld(); // C26800// 可以使用以下方法判断是否为空指针if (um_brain==nullptr) {
std::cout<<"um_brain is nullptr"<<std::endl;
}
// 可以释放资源将指针恢复空指针um_brain.reset();

 

====================================

shared_ptr

和 unique 不同的是,它允许自身对象(shared_ptr)被复制,复制出来的 shared_ptr 所托管的指针都指向同一块内存空间。而它的每一份拷贝(shared_ptr自身)都会有一个引用计数,资源的释放由生命周期中最后一个 shared_ptr 负责。因此 shared_ptr 是最常用的智能指针,也是最容易出问题的智能指针。

使用它时应当注意:

1,不要将已存在裸指针交由 shared_ptr,任何形式的智能指针都不应该去托管已有的裸指针。

2,作为函数参数传递时,请传递引用。因为作为值传递时,将产生大量无意义的引用计数。

3,共享所有权性质的对象往往比限定作用域的对象生存时间更久、资源开销更大,尤其是多线程下。

 

// 创建shared_ptr<Brain>s_brain=make_shared<Brain>();
s_brain->HelloWorld();
// 复制shared_ptr<Brain>c_brain=s_brain;
shared_ptr<Brain>d_brain=s_brain;
// 检查唯一性std::cout<<s_brain.unique() <<std::endl;
// 检查引用计数数量std::cout<<s_brain.use_count() <<std::endl;

 

与 shared_ptr 搭配的 weak_ptr

weak_ptr 设计上与 shared_ptr 搭配使用,因为 shared_ptr 存在一个问题,就是循环引用计数递增而导致的内存泄漏。

比如说:

【伪代码】

classnode{
shared_ptr<node>start;
shared_ptr<node>end;
}
shared_ptr<node>nn=make;
shared_ptr<node>mm=make;
nn->end=mm;
mm->start=nn;

 

这种情况,两个node对象互相引用着对方,最终导致资源无法被释放。所以有时候需要访问 shared_ptr 自身,而不是它所托管的资源。

所以 weak_ptr 的作用就来了:

【伪代码】

classnode{
weak_ptr<node>start;
weak_ptr<node>end;
}
shared_ptr<node>nn=make;
shared_ptr<node>mm=make;
nn->end=mm;
mm->start=nn;

 

为什么呢?

因为 share_ptr 是强引用,强引用是只要被引用的对象还存活那么这个引用也一定会存在。

而 weak_ptr 是弱引用,弱引用是虽然对象还活着,但是这个引用则可有可无。

所以,weak_ptr 的作用就是作为一个 "观察者" 访问 shared_ptr 本身,而不是 shared_ptr 所托管的资源。

同时也意味着,weak_ptr 只能访问它所观察的 shared_ptr 本身,而不能访问 share_ptr 托管的资源,所以,它不会增加 shared_ptr 的引用计数。

 

shared_ptr<Brain>r_brain=make_shared<Brain>();
// 创建 weak_ptrweak_ptr<Brain>w_brain(r_brain);
// 检查 shared 引用计数数量std::cout<<w_brain.use_count() <<std::endl;
// 返回一个原始 shared 的拷贝(增加引用计数)shared_ptr<Brain>e_brain(w_brain.lock());
std::cout<<w_brain.use_count() <<std::endl;

 

====================================

make_unique 与 make_shared

这两个标准库函数是用于创建智能指针的函数。

【以下懒得打字了直接抄的Docs,重点我划出来】

 

autosp=std::shared_ptr<Example>(newExample(argument));
automsp=std::make_shared<Example>(argument);

 

使用make_shared作为创建对象的简单、更高效的方法,以及一个shared_ptr来同时管理对对象的共享访问。 在语义上,这两个语句是等效的。但是,第一条语句进行了两个分配,如果在shared_ptr对象的分配成功后,Example的分配失败,则未命名的Example对象将被泄漏。 使用make_shared的语句更简单,因为只涉及到一个函数调用。 这样会更有效,因为库可能会对对象和智能指针进行一个分配。 此函数的速度更快,导致内存碎片更少,但在一次分配时不存在异常,而不是在另一种分配上。 通过使引用对象和更新智能指针中的引用计数的代码具有的更好的地址来提高性能。

make_unique 如果不需要对对象的共享访问权限,请考虑使用。 allocate_shared 如果需要为对象指定自定义分配器,请使用。 make_shared如果对象需要自定义删除器,则不能使用,因为无法将删除器作为参数传递。

--Microsoft Docs






====================================

芯片烤电池 C++ Example 2022-Spring Season Pass :

【Example】C++ 标准库常用容器全面概述

【Example】C++ 回调函数及 std::function 与 std::bind

【Example】C++ 运算符重载

【Example】C++ 标准库智能指针 unique_ptr 与 shared_ptr

【Example】C++ 接口(抽象类)概念讲解及例子演示

【Example】C++ 虚基类与虚继承 (菱形继承问题)

【Example】C++ Template (模板)概念讲解及编译避坑

【Example】C++ 标准库 std::thread 与 std::mutex

【Example】C++ 标准库多线程同步及数据共享 (std::future 与 std::promise)

【Example】C++ 标准库 std::condition_variable

【Example】C++ 用于编译时封装的 Pimpl 演示 (编译防火墙 Private-IMPL)

【Example】C++ 单例模式 演示代码 (被动模式、兼容VS2022编译)

====================================

相关文章
|
8天前
|
消息中间件 存储 开发工具
消息队列 MQ产品使用合集之C++如何使用Paho MQTT库进行连接、发布和订阅消息
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。
|
6天前
|
存储 安全 Linux
网络请求的高效处理:C++ libmicrohttpd库详解
网络请求的高效处理:C++ libmicrohttpd库详解
|
12天前
|
存储 安全 编译器
【C++航海王:追寻罗杰的编程之路】引用、内联、auto关键字、基于范围的for、指针空值nullptr
【C++航海王:追寻罗杰的编程之路】引用、内联、auto关键字、基于范围的for、指针空值nullptr
36 5
|
11天前
|
C++ 容器
【编程技巧】 C++11智能指针
C++11引入了智能指针以自动管理内存,防止内存泄漏和悬挂指针: - `shared_ptr`:引用计数,多所有权,适用于多个对象共享资源。 - `unique_ptr`:独占所有权,更轻量级,适用于单一对象所有者。 - `weak_ptr`:弱引用,不增加引用计数,解决`shared_ptr`循环引用问题。 ## shared_ptr - 支持引用计数,所有者共同负责资源释放。 - 创建方式:空指针、new操作、拷贝构造/移动构造,以及自定义删除器。 - 提供`operator*`和`operator-&gt;`,以及`reset`、`swap`等方法。 ## unique_ptr
203 0
|
12天前
|
C++
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
13 0
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
|
16小时前
|
存储 C++ 容器
C++一分钟之-正则表达式库(regex)
【7月更文挑战第7天】C++从C++11开始支持正则表达式,通过`&lt;regex&gt;`库提供功能。本文涵盖基本概念如`std::regex`、`std::smatch`,以及`regex_search`和`regex_match`的使用。常见问题包括大小写敏感性、特殊字符转义、贪婪与非贪婪匹配和捕获组。提供的代码示例展示了如何进行匹配、不区分大小写的匹配、特殊字符匹配、贪婪与非贪婪匹配和捕获组的使用。理解并练习正则表达式能提升文本处理效率。
6 0
|
4天前
|
存储 算法 程序员
C++基础知识(八:STL标准库(Vectors和list))
C++ STL (Standard Template Library标准模板库) 是通用类模板和算法的集合,它提供给程序员一些标准的数据结构的实现如 queues(队列), lists(链表), 和 stacks(栈)等. STL容器的提供是为了让开发者可以更高效率的去开发,同时我们应该也需要知道他们的底层实现,这样在出现错误的时候我们才知道一些原因,才可以更好的去解决问题。
|
4天前
|
算法 前端开发 C++
C++基础知识(八:STL标准库 deque )
deque在C++的STL(Standard Template Library)中是一个非常强大的容器,它的全称是“Double-Ended Queue”,即双端队列。deque结合了数组和链表的优点,提供了在两端进行高效插入和删除操作的能力,同时保持了随机访问的特性。
|
4天前
|
存储 C++ 索引
C++基础知识(八:STL标准库 Map和multimap )
C++ 标准模板库(STL)中的 map 容器是一种非常有用的关联容器,用于存储键值对(key-value pairs)。在 map 中,每个元素都由一个键和一个值组成,其中键是唯一的,而值则可以重复。