《C++ Primer》第12章 动态内存(二)

参考资料:

  • 《C++ Primer》第5版
  • 《C++ Primer 习题集》第5版

12.1.5 unique_ptr(P417)

unique “拥有”它所指向的对象,某个时刻只能有一个 unique_ptr 指向一个给定对象。

381a5b5e846786f8c43170926fca976

当我们定义一个 unique_ptr 时,需要将其绑定到一个 new 返回的指针上,且必须采用直接初始化的形式:

unique_ptr<double> p1;    // 空unique_ptr
unique_ptr<int> p2(new int());    // 指向一个值初始化的int

unique_ptr 不支持普通的拷贝和赋值操作:

unique_ptr<string> p1(new string("hello"));
unique_ptr<string> p2(p1);    // 错误
unique_ptr<string> p3;
p3 = p2;    // 错误

我们可以通过 releasereset 将指针的所有权从一个(非 constunique_ptr 转移给另一个 unique_ptr

unique_ptr<string> p1(new string("hello"));
unique_ptr<string> p2(p1.release());
unique_ptr<string> p3;
p3.reset(p2.release());

传递unique_ptr参数和返回unique_ptr

不能拷贝 unique_ptr 的规则有一个例外:我们可以拷贝或赋值一个将要被摧毁的 unique_ptr

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

unique_ptr传递删除器

unique 默认使用 delete 释放指向的对象。与 shared_ptr 不同的是,我们需要在构造 unique_ptr 时提供删除器的类型:

unique_ptr<objT, delT> p(new objT, fcn);

以上一篇笔记中提到的网络连接类为例:

void f(destination &d /* 其他参数 */) {connection c = connect(&d);unique_ptr<connection, decltype(end_connection)*>p(&c, end_connection);// 当p被销毁时,调用end_connection
}

12.1.6 weak_ptr(P420)

weak_ptr 是一种不控制所指向对象生存期的智能指针,指向由一个 shared_ptr 管理的对象。将 weak_ptr 绑定到一个 shared_ptr 不会改变 shared_ptr 的引用计数,一旦最后一个指向对象的 shared_ptr 被销毁,即使有 weak_ptr 指向对象,对象也还是会被释放。

a99c3cc2f6bec742c0f663d36af300e

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

auto p = make_shared<int>();
weak_ptr<int> wp(p);

上述代码中 wp 不会改变 p 的引用计数。由于 wp 指向的对象可能被释放掉,我们不能使用 weak_ptr 直接访问对象,而必须调用 lock

if(shared_ptr<int> np = wp.lock()){    // 如果np不为空则条件成立...
}

核查指针类

为了展示 weak_ptr 的用途,我们为 StrBlob 类定义一个伴随指针类 StrBlobPtr ,类中保存一个 weak_ptr ,指向 StrBlobdata 成员。使用 weak_ptr 可以阻止用户访问一个不再存在的 vector

class StrBlobPtr {
public:StrBlobPtr(): curr(0) {}StrBlobPtr(StrBlob &a, size_t sz = 0) :wptr(a.data), curr(sz){}string &deref() const;StrBlobPtr &incr();
private:shared_ptr<vector<string>> check(size_t, const string &) const;weak_ptr<vector<string>> wptr;size_t curr;    // 在数组中的当前位置
};

StrBlobPtrcheck 成员和 StrBlob 中的同名成员不同,它还要额外检查指向的 vector 是否存在:

shared_ptr<vector<string>>
StrBlobPtr::check(size_t i, const string &msg)const {auto ret = wptr.lock();if (!ret)throw runtime_error("unbound StrBlobPtr");if (i >= ret->size())throw out_of_range(msg);return ret;
}

指针操作

我们定义 derefincr 用来解引用和递增 StrBlobPtr

string &StrBlobPtr::deref()const {auto p = check(curr, "dereference past end");return (*p)[curr];
}StrBlobPtr &StrBlobPtr::incr() {check(curr, "increment past end of StrBlobPtr");++curr;return *this;
}

由于我们在初始化 StrBlobPtr 时需要用到 StrBlob 中的 data 成员,所以我们要将 StrBlobPtr 声明成 StrBlob 的友元。

12.2 动态数组(P423)

C++ 和标准库提供了两种一次分配一个对象数组的方法。在大多数情况下,我们应该使用容器而非动态数组,使用容器的类可以使用默认版本的拷贝、赋值、析构操作,而使用动态数组的类必须定义自己版本的操作。

new和数组(P423)

为了让 new 分配一个对象数组,我们要在类型名后跟一对方括号,在其中指明要分配的对象的数目:

int *pia = new int[get_size()];    // 方括号中必须为整型,但不必为常量

也可以用类型别名来分配数组:

using arrT = int[1024];
int *p = new arrT;

分配一个数组会得到一个元素类型的指针

无论用 new T[] 还是类型别名,我们得到的都是一个指向数组元素类型的指针,而不是一个数组。下面的代码验证了这个事实:

int x = 0;
decltype(new int[10]) p1 = &x;    // 正确
int arr[10];
decltype(arr) p2 = &x;    // 错误

动态数组并不是数组类型

初始化动态分配对象的数组

默认情况下,new 分配的对象,不论是单个对象还是数组,都是默认初始化的。要对数组中的元素执行值初始化,可以在大小后跟一对圆括号:

int *pia1 = new int[10];    // 10个默认初始化的int
int *pia2 = new int[10]();    // 10个值初始化的int

在新标准中,我们还可以提供初始值列表:

int *pia3 = new int[10] {0, 1, 2, 3};

动态分配一个空数组是合法的

char arr[0];    // 错误
char *cp = new char[0];    // 正确

当我们用 new 分配一个大小为 0 的数组时,new 返回一个合法的非空指针。

释放动态数组

为了释放动态数组,我们也要在 delete 后跟一对方括号:

delete p;    // p必须指向一个动态分配的对象或为空
delete [] pa;    // pa必须指向一个动态分配的对象数组或为空

数组中的元素按逆序销毁。如果我们在 delete 一个数组时忽略了方括号或在 delete 一个对象时使用了方括号,结果是未定义的。

前面提到,当我们使用类型别名来定义数组类型时,在 new可以不使用方括号,但是在 delete 时则必须使用方括号:

using arrT = int[1024];
auto p = new arrT;
delete[] p;

此处产生一个疑问,既然前面提到,new[] 得到的仅仅是一个指针,而并不是一个数组,那么 delete[] 是怎么知道需要释放多少空间的呢?答案见C++中delete是如何获知需要释放的内存(数组)大小的? - 知乎 (zhihu.com)

智能指针和动态数组

标准库提供了一个可以管理 new 分配的数组的 unique_ptr 版本:

unique_ptr<int[]> up(new in[10]);
up.release();    // 自动使用delete[] 
4540dd42fbdbdd927fbe0ab578c2939

unique_str 指向一个数组时,我们不能使用点运算符箭头运算符,但我们可以使用下标运算符访问数组中的元素。

shared_ptr 不支持直接管理动态数组。如果希望使用 shared_ptr 管理动态数组,必须定义自己的删除器:

shared_ptr<int> sp(new int[10], [](int *p) {delete[] p; });

如果未提供删除器,shared_ptr 将使用 delete 释放一个动态数组,这个行为是未定义的。由于 shared_ptr 不支持下标运算符,为了访问访问数组中的元素,必须用 get 获得一个内置指针:

for (size_t i = 0; i != 10; ++i) {*(sp.get() + i) = i;
}

12.2.2 allocator类(P427)

new 在灵活性上有一些局限,因为它将内存分配和对象构造组合在一起了。当分配一大块内存时,我们通常希望将内存分配和对象构造分离,而将内存分配和对象构造组合在一起可能造成不必要的浪费:

// 初始化了n个string,但某些string可能永远用不到
string *const p = new string[n];

此外,没有默认构造函数的类不能用 new 分配动态数组。

allocator

allocator 类定义在头文件 memory 中,它帮助我们将内存分配和对象构造分离开来。

7503fa1c99d8e1eb3b70fa1f0002f50
allocator<string> alloc;
const auto p = alloc.allocate(n);     // 分配n个未初始化的string

allocator分配未构造的内存

auto q = p;    // 顶层const被忽略
alloc.construct(q++);
alloc.construct(q++, 10, 'c');
alloc.construct(q++, "hi");

当我们用完对象后,必须对每个元素调用 destroy 销毁它们:

while(q != p){alloc.destroy(--q);    // 释放真正构造的string
}

调用 deallocate 释放内存:

alloc.deallocate(p, n);

拷贝和填充未初始化内存的算法

标准库还为 allocator 类定义了两个伴随算法,定义在头文件 memory 中:

d40ebd3f94a185266f1296424db3535
allocator<string> alloc;
vector<string> vs = {"hello", "hi", "him"};
auto p = alloc.allocate(vs.size() * 2);
auto q = uninitialized_copy(vs.begin(), vs.end(), p);
uninitialized_fill_n(q, vs.size(), "world");
d40ebd3f94a185266f1296424db3535
allocator<string> alloc;
vector<string> vs = {"hello", "hi", "him"};
auto p = alloc.allocate(vs.size() * 2);
auto q = uninitialized_copy(vs.begin(), vs.end(), p);
uninitialized_fill_n(q, vs.size(), "world");

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

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

相关文章

游戏中小地图的制作__unity基础开发教程

小地图的制作 Icon标识制作制作摄像机映射创建地图UI效果“不一样的效果” 在游戏中经常可以看到地图视角的存在&#xff0c;那么地图视角是如何让实现的呢&#xff1f; 这一期教大家制作一个简易的小地图。 &#x1f496;点关注&#xff0c;不迷路。 老样子&#xff0c;我们还…

Leetcode 131 分割回文串

题意理解&#xff1a; 分割回文子串&#xff0c;可以看作是划分连续的字幕组合——所以也可以用回溯的方法来解决 每个位置选与不选——该位置切割|不切割 对于每一段子串——>判断是否是回文串&#xff1a; 是&#xff1a; 继续切割 不是&#xff1a; 剪枝 解题方法…

Ubuntu Destktop 22.04 设置 ssh 超时时间

Ubuntu Destktop 22.04 使用 ssh 连接服务器时&#xff0c;发现一段时间不操作就会自动断开连接&#xff0c;解决方法如下&#xff1a; 打开 /etc/ssh/ssh_config 文件&#xff1a; sudo vim /etc/ssh/ssh_config在文件最后添加&#xff1a; # ssh 客户端会每隔 30 秒发送一…

在线免费制作各种证件照,有需要的收藏

现在很多场合都需要一寸证件照&#xff0c;比如办理身份证、出国签证等。以往&#xff0c;我们都需要到专门的照相馆拍摄&#xff0c;但是现在&#xff0c;有了随时照微信小程序&#xff08;抖音和支付搜索亿鸣证件照哦&#xff09;&#xff0c;你可以足不出户就能够制作一寸证…

MySQL InnoDB Replication部署方案与实践

1. 概述 MySQL Innodb ReplicaSet 是 MySQL 团队在 2020 年推出的一款产品&#xff0c;用来帮助用户快速部署和管理主从复制&#xff0c;在数据库层仍然使用的是主从复制技术。 ReplicaSet 主要包含三个组件&#xff1a;MySQL Router、MySQL Server 以及 MySQL Shell 高级客户…

12 位多通道国产芯片ACM32F403/F433 系列,支持 MPU 存储保护功能,应用于工业控制,智能家居等产品中

ACM32F403/F433 芯片的内核基于 ARMv8-M 架构&#xff0c;支持 Cortex-M33 和 Cortex-M4F 指令集。芯片内核 支持一整套DSP指令用于数字信号处理&#xff0c;支持单精度FPU处理浮点数据&#xff0c;同时还支持Memory Protection Unit &#xff08;MPU&#xff09;用于提升应用的…

PyTorch张量:内存布局

你可能对 torch 上的某些函数感到困惑&#xff0c;它们执行相同的操作但名称不同。 例如&#xff1a; reshape()、view()、permute()、transpose() 等。 这些函数的做法真的不同吗&#xff1f; 不&#xff01; 但为了理解它&#xff0c;我们首先需要了解一下张量在 pytorch 中…

1 CPU实现的基本框图

汇编语言 && 指令格式 CPU设计的框架&#xff1a;三级流水线 ROM存放指令和数据&#xff0c;大端模式&小端模式&#xff0c;地址对齐 取指 译码&#xff1a; 执行&#xff1a; 汇编语言 & 指令格式 流水线实现工作机制 模块功能划分&接口信号 参考…

strict-origin-when-cross-origin

严格限制同源策略 &#xff08;1&#xff09;允许服务器的同源IP地址访问 &#xff08;2&#xff09;允许Referer --- 后端服务器要配置

linux sed命令删除一行/多行_sed删除第一行/linux删除文件某一行

sed系列文章 linux常用命令(9)&#xff1a;sed命令(编辑/替换/删除文本)linux sed命令删除一行/多行_sed删除第一行/linux删除文件某一行 文章目录 sed系列文章一、sed删除某一行内容/删除最后一行二、sed删除多行三、扩展3.1、-i命令 本文主要讲解如何删除txt文件中的某一行内…

企业使用APP自动化测试工具的重要因素

随着移动应用市场的蓬勃发展&#xff0c;企业对高质量、高效率的软件交付提出了更高的要求。在这个背景下&#xff0c;APP自动化测试工具成为了企业不可或缺的一部分。以下是企业采用APP自动化测试工具的关键因素&#xff1a; 1. 快速且可重复的测试执行 自动化测试工具能够快速…

做数据分析为何要学统计学(5)——什么问题适合使用卡方检验?

卡方检验作为一种非常著名的非参数检验方法&#xff08;不受总体分布因素的限制&#xff09;&#xff0c;在工程试验、临床试验、社会调查等领域被广泛应用。但是也正是因为使用的便捷性&#xff0c;造成时常被误用。本文参阅相关的文献&#xff0c;对卡方检验的适用性进行粗浅…

原来使用代码也可以画时序图,用这个Mermaid就行,真香

本文首发于我的个人掘金博客&#xff0c;看到很多人都比较喜欢这篇文章&#xff0c;分享给大家。 个人博客主页&#xff1a;https://www.aijavapro.cn 个人掘金主页&#xff1a;juejin.cn/user/2359988032644541/posts 个人知识星球: 觉醒的新世界程序员 一、背景 在软件开发和…

spring数据校验

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 本…

数据库(一)| 数据库概述、基本概念、关系型数据库特点、超键候选码等

文章目录 1 数据库的一些基础概念1.1 数据库和数据库管理系统1.2 关系模式和关系实例1.3 数据库模式和数据库实例 2 数据库组织形式2.1 数据采用文件的缺点2.2 使用数据库管理系统的 优点 3 关系型数据库特点4 三个层次的数据抽象Data Abstraction5 超键、候选码、主码、外码 1…

php之jwt使用

PHP JWT&#xff08;JSON Web Token&#xff09;是一种用于身份验证和授权的开放标准。JWT是一个包含有关用户或实体身份信息的安全令牌&#xff0c;它由三部分组成&#xff1a;头部&#xff08;Header&#xff09;、载荷&#xff08;Payload&#xff09;和签名&#xff08;Sig…

计算机网络编程

网络编程 Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机&#xff0c; Java 仍是企业和开发人员的首选开发平台。 课程内容的介绍 1. 计算机网络基础 2. So…

数据结构基础介绍

一.起源及重要性 1968 年&#xff0c;美国的高德纳 Donakl E . Kn uth 教授在其所写的《 计算机程序艺术》第一卷《基本算法 》 中&#xff0c;较系统地阐述了数据的逻辑结构和存储结构及其操作&#xff0c; 开创了数据结构的课程体系 &#xff0c;数据结构作为一门独立的…

B029-JDBC增强

目录 PreparedStatement 查询1.sql注入2.Statement分析 (面试题)3.PreparedStatement (面试题) 登录功能的完善事务链接池概念实现DBCP连接池实现第一种配置方式第二种配置方式 返回主键BaseDao的抽取 PreparedStatement 查询 1.sql注入 就是在sql的字符串拼接的时候&#xf…

基于单片机的定时插座在智能家居中的应用

近年来&#xff0c;随着科学技术的发展迅速&#xff0c;人们对智能化的要求越来越高。越来越多的智能化产品进入千家万户&#xff0c;如电脑电视、扫地机器人、智能空气净化器等。这些家居电器和电子产品大都需要连接电源&#xff0c;为满足多种用电器的正常使用&#xff0c;延…