【现代C++】第二部分 C++标准库(5) 动态内存与智能指针、动态数组、文本查询程序

本文探讨了C++中如何使用智能指针(如shared_ptr、unique_ptr)来管理和释放动态内存,避免内存泄漏。通过实例讲解shared_ptr的make_shared、拷贝和赋值,以及如何在StrBlob类中应用这些概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文属于「现代C++学习实践」系列文章之一,这一系列正式开始于2021/09/04,着重于现代C++(即C++11、14、17、20、23等新标准)和Linux C++服务端开发的学习与实践。众所周知,「C++难就难在:在C++中你找不到任何一件简单的事」。因此,本系列将至少持续到作者本人「精通C++」为止(笑)。由于文章内容随时可能发生更新变动,欢迎关注和收藏现代C++系列文章汇总目录一文以作备忘。

为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/Modern-Cxx-Learning-Path。在这一仓库中,你可以看到本人学习C++的全过程,包括C++书籍源码、练习实现、小型项目等。

需要特别说明的是,为了透彻理解和全面掌握现代C++,本系列文章中参考了诸多博客、教程、文档、书籍等资料,限于时间精力有限,这里无法一一列出。部分重要资料的不完全参考目录如下所示,在后续学习整理中还会逐渐补充:

  • C++ Primer 中文版(第5版),Stanley B. Lippman、Barbara E. Moo等著,王刚、杨巨峰译,叶劲峰、李云、刘未鹏等审校,电子工业出版社;
  • Bjarne Stroustrup老爷子的个人网站。包括Thriving in a Crowded and Changing World: C++ 2006–2020及其中文版——在拥挤和变化的世界中茁壮成长:C++ 2006–2020(一份了解标准化背后故事、以及C++未来发展方向的绝佳材料)
  • 侯捷老师的公开课;
    • C++面向对象高级开发上、下:正确理解面向对象的精神和实现手法,涵盖对象模型、关键机制、编程风格、动态分配;
    • STL标准库与范型编程:深入剖析STL标准库之六大部件、及其之间的体系结构,并分析其源码,引导高阶泛型编程。
    • C++新标准C++11/14:在短时间内深刻理解C++2.0的诸多新特性,涵盖语言和标准库两层
    • C++内存管理机制:学习由语言基本构件到高级分配器的设计与实作,并探及最低阶malloc 的内部实现。
    • C++ Startup揭密:C++程序的生前和死后。认识Windows平台下的Startup Code(启动码),完全通透C++程序的整个运行过程。

目前为止,我们编写的程序中所使用的对象,都有着严格定义的生存期:

  • 全局对象在程序启动时分配,在程序结束时销毁;
  • 对于局部 static 对象,在第一次使用前分配,在程序结束时销毁;
  • 对于局部自动对象,当我们进入其定义所在的程序块时被创建,在离开块时销毁。

对应地,目前为止,我们的程序只使用过静态内存栈内存

  • 静态内存用来保存局部 static 对象(参见6.6.1节,第185页)、static 数据成员(参见7.6节,第268页)以及定义在任何函数之外的变量(即全局对象)。
  • 栈内存用来保存定义在函数内的非 static 对象(即局部自动对象)。

分配在静态或栈内存中的对象,由编译器自动创建和销毁:

  • static 对象在使用之前分配,在程序结束时销毁;
  • 对于栈对象,仅在其定义的程序块运行时才存在。

除了 static 和自动对象外,C++还支持动态分配对象。动态分配的对象的生存期与它们在哪里创建是无关的,只有当显式地被释放时,这些对象才会销毁

对应地,除了静态内存和栈内存外,每个程序还拥有一个内存池,这部分内存被称作自由空间 free store heap 。程序用堆来存储动态分配 dynamically allocate 的对象——即那些在程序运行时分配的对象。动态对象的生存期由程序来控制,当动态对象不被使用时,我们的代码必须显式地销毁它们。

然而,动态对象的正确释放,被证明是编程中极其容易出错的地方。为了更安全地使用动态对象,标准库定义了两个智能指针类型,以管理动态分配的对象。当一个对象应该被释放时,指向它的智能指针可以确保自动地释放它


1. 动态内存与智能指针

在C++中,动态内存的管理是通过一对运算符来完成的:

  • new ,在动态内存中为对象分配空间,并返回一个指向该对象的指针;
  • delete ,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。

动态内存的使用很容易出问题,因为确保在正确的时间释放内存是极其困难的。有时我们会忘记释放内存,这种情况下就会产生内存泄露;有时在尚有指针引用内存的情况下,我们就释放了它,这种情况下就会产生引用非法内存的指针。

为了更容易、更安全地使用动态内存,C++11标准库提供了两种智能指针 smart pointer 类型来管理动态对象。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。新标准库提供的这两种智能指针的区别在于管理底层指针的方式shared_ptr 允许多个指针指向同一个对象;unique_ptr 则独占所指向的对象。标准库还定义了一个名为 weak_ptr 的伴随类,它是一种弱引用,指向 shared_ptr 所管理的对象。这三种类型都定义在 memory 头文件中。

1.1 shared_ptr

类似 vector ,智能指针也是类模板(参见3.3节,第86页)。因此创建一个智能指针时,编译器无法像使用函数模板一样从函数实参推断模板实参,我们必须提供额外的信息——指针可以指向的类型。与 vector 一样,我们在尖括号内给出类型,之后是所定义的这个智能指针的名字:

shared_ptr<string> p1;	  // shared_ptr,可以指向string
shared_ptr<list<int>> p2; // shared_ptr,可以指向int的list

默认初始化的智能指针中保存着一个空指针(参加2.3.2节,第48页),在1.3节中,我们将介绍初始化智能指针的其他方法。

智能指针的使用方式与普通指针类似。解引用一个智能指针以返回它指向的对象(的引用)。如果在一个条件判断中使用智能指针,效果就是检测它是否为空:

// 如果p1不为空,检查它是否指向一个空string
if (p1 && p1->empty())
	*p1 = "hi"; // 如果p1指向一个空string,解引用p1,将一个新值赋予string

表12.1列出了 shared_ptrunique_ptr 都支持的操作:

share_ptrunique_ptr 都支持的操作操作说明
shared_ptr<T> sp空智能指针,可以指向类型为 T 的对象,里面保存着一个空指针
unique_ptr<T> up;同上
pp 用作一个条件判断,若 p 指向一个对象,则为 true
*p解引用 p ,获取它指向的对象
p->mem等价于 (*p).mem
p.get()返回 p 中保存的指针。要小心使用,若智能指针释放了其对象,返回的指针所指向的对象也就消失了
swap(p, q)交换 pq 中的指针
p.swap(q)同上

只适用于 shared_ptr 的操作列在表12.2中:

share_ptr 独有的操作操作说明
make_shared<T>(args)返回一个 shared_ptr ,指向一个动态分配的类型为 T 的对象,并使用 args 初始化此对象
shared_ptr<T> p(q)pshared_ptr q 的拷贝。此操作会递增 q 中的计数器。q 中的指针必须能转换为 T*(参加4.11.2节,第143页)
p = qpq 都是 shared_ptr ,所保存的指针必须能相互转换。此操作会递减 p 的引用计数,递增 q 的引用计数。若 p 的引用计数变为 0 ,则将其管理的原内存释放
p.unique()p.use_count()1 ,返回 true ,否则返回 false
p.use_count()返回与 p 共享对象的智能指针数量,可能很慢,主要用于调试

1.1.1 make_shared 函数

1.1.2 shared_ptr 的拷贝和赋值

1.1.3 shared_ptr 自动销毁所管理的对象

1.1.4 使用了动态生存期的资源的类

1.1.5 定义 StrBlob

1.1.6 StrBlob 构造函数

1.1.7 元素访问成员函数

1.1.8 StrBlob 的拷贝、赋值和销毁

⭐️12.1.1节练习

练习12.1:在此代码的结尾,b1b2 各包含多少个元素?

StrBlob b1;
{
	StrBlob b2 = {"a", "an", "the"};
	b1 = b2;
	b2.push_back("about");
}

练习12.2:编写你自己的 StrBlob 类,包含 const 版本的 frontback()

练习12.3:StrBlob 需要 const 版本的 push_backpop_back 吗?如果需要,添加进去。否则,解释为什么不需要。

练习12.4:在我们的 check 函数中,没有检查 i 是否大于 0 。为什么可以忽略这个检查?

练习12.5:我们未编写接受一个 initializer_list explicit(参加7.5.4节,第264页)的构造函数。讨论这个设计策略的优点和缺点。


1.2 直接管理内存

1.3 shared_ptrnew 结合使用

1.4 智能指针和异常

1.5 unique_ptr

1.6 weak_ptr


2. 动态数组

2.1 new 和数组

2.2 allocator


3. 使用标准库:文本查询程序

3.1 文本查询程序设计

3.2 文本查询程序类的定义

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

memcpy0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值