C++ Primer 第五版 第12章 动态内存

动态分配的对象的生存期与它们在哪里创建的是无关的,只有当显式地被释放时,这些对象才会销毁。

为了更安全地使用动态对象,标准库定义了两个智能指针类型来管理动态内存分配的对象。当一个对象应该被释放时,指向它的智能指针可以确保自动地释放它。

静态内存用来保存局部static对象,类static数据成员以及定义在任何函数之外的变量。

栈内存用来保存定义在函数内的非static对象。

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

除了静态内存和栈内存,每个程序还拥有一个内存池。这部分内存被称为自由空间(free store)或(heap)。程序用堆来存储动态分配(dynamically allocate)的对象——即,那些在程序运行时分配的对象。

一、动态内存与智能指针

在C++中,动态内存的管理是通过一对运算符来完成的:new,在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化;delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。

标准库提供了两种智能指针(smart pointer)类型来管理动态对象。这两种智能指针的区别在于管理底层指针的方式:shared_ptr允许多个指针指向同一个对象;unique_ptr则“独占”所指向的对象。标准库还定义了一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。这三种类型都定义在memory头文件中。

智能指针是模板。当我们创建一个智能指针时,必须提供额外的信息——指针可以指向的类型。和vector一样,我们在尖括号内给出类型,之后是所定义的这种智能指针的名字。默认初始化的智能指针中保存着一个空指针。

1. shared_ptr类

make_shared函数

最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。与智能指针一样,make_shared也定义在头文件memory中。

make_shared用其参数来构造给定类型的对象。传递的参数必须与该类型的某个构造函数相匹配。如果我们不传递任何参数,对象就会进行值初始化。

通常用auto定义一个对象来保存make_shared的结果。

shared_ptr的拷贝和赋值

当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。我们可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数(reference count)。一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的的对象。

当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象。它是通过另一个特殊的成员函数——析构函数(destructor)完成销毁工作的。每个类都有一个析构函数,用于控制次类型的对象销毁时做什么操作。析构函数一般用来释放对象所分配的资源。

定义StrBlob类

定义一个名为StrBlob的类,保存一组元素。与容器不同,我们希望StrBlob对象的不同拷贝之间共享相同的元素。即,当我们拷贝一个StrBlob时,原StrBlob对象及其拷贝应该引用相同的底层元素。

我们使用vector来保存元素。将vector保存在动态内存中,为每个StrBlob设置一个shared_ptr来管理动态分配的vector。此shared_ptr的成员将记录有多少个StrBlob共享相同的vector,并在vector的最后一个使用者销毁时释放vector。

StrBlob构造函数

元素访问成员函数

访问元素之前必须检查元素是否存在。check函数检查一个给定索引是否在合法范围内。除了索引,check还接受一个string参数,它会将此参数传递给异常处理程序,这个string描述了错误内容。

2. 直接管理内存

C++语言定义了两个运算符来分配和释放动态内存。运算符new分配内存,delete释放new分配的内存。

自己直接管理内存的类与使用智能指针的类不同,它们不能依赖于类对象拷贝、赋值和销毁操作的任何默认定义。

使用new动态分配和初始化对象

在自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回一个指向该对象的指针:

此new表达式在自由空间构造一个int型对象,并返回指向该对象的指针。

默认情况下,动态分配的对象是默认初始化的,这意味着内置类型或组合类型的对象的值将是未定义的,而类类型对象将用默认构造函数进行初始化:

我们可以使用直接初始化方式来初始化一个动态分配的对象。

也可以对动态分配的对象进行值初始化,只需在类型名之后跟一对空括号即可:

只有当括号中仅有单一初始化器时才可以使用auto:

动态分配的const对象

用new分配const对象是合法的:

内存耗尽

一旦一个程序用光了它所有可用的内存,new表达式就会失败。默认情况下,如果new不能分配所要求的内存空间,它会抛出一个类型为bad_alloc的异常。我们可以改变使用new的方式来阻止它抛出异常:

我们称这种形式的new为定位new(placement new)。定位new表达式允许我们向new传递额外的参数。在此例中,我们传递给它一个由标准库定义的名为nothrow的对象。如果nothrow传递给new,我们的意图是告诉它不能抛出异常。bad_alloc和nothrow都定义在头文件new中。

释放动态内存

delete表达式执行两个动作:销毁给定的指针所指向的对象;释放对应的内存。

释放一块非new分配的内存,或者将相同的指针值释放多次,其行为是未定义的。

由内置指针(而不是智能指针)管理的动态内存在被显式释放前一直都会存在。

与类类型不同,内置类型的对象被销毁时什么也不会发生。

delete之后重置指针值

当我们delete一个指针后,指针变成空悬指针(dangling pointer),即,指向一块曾经保存数据对象但现在已经无效的内存的指针。

为了避免空悬指针:在指针即将要离开其作用域之前释放掉它所关联的内存。如果我们需要保留指针,可以在delete之后将nullptr赋予指针,这样就清楚地指出指针不指向任何对象。

3. shared_ptr和new结合使用

如果我们不初始化一个智能指针,它就会被初始化为一个空指针。我们可以用new返回的指针来初始化智能指针。

接受指针参数的智能指针构造函数是explicit的。因此,我们不能将一个内置指针隐式转换为一个智能指针,必须使用直接初始化形式来初始化一个智能指针。

不要混合使用普通指针和智能指针,也不要使用get初始化另一个智能指针或为智能指针赋值。

智能指针类型定义了一个名为get的函数,它返回一个内置指针,指向智能指针管理的对象。此函数是为了这样一种情况而设计的:我们需要向不能使用智能指针的代码传递一个内置指针。使用get返回的指针的代码不能delete此指针。

4. 智能指针和异常

5. unique_ptr

与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁。

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

unique_ptr不支持普通的拷贝或赋值操作。

6. weak_ptr

weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。

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

由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock。

二、动态数组

C++语言定义了另一种new表达式语法,可以分配并初始化一个对象数组。标准库中包含一个名为allocator的类,允许我们将分配和初始化分离。使用allocator通常会提供更好的性能和更灵活的内存管理能力。

1. new和数组

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

也可以用一个表示数组类型的类型别名来分配一个数组。

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

动态数组不是数组类型,因此不能对动态数组调用begin或end,也不能用范围for语句来处理(所谓的)动态数组中的元素。

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

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

释放动态数组

为了释放动态数组,我们使用一种特殊形式的delete——在指针前加上一个方括号对。

数组中的元素按逆序销毁,即最后一个元素首先被销毁,然后是倒数第二个,依此类推。

智能指针和动态数组

标准库提供了一个可以管理new分配的数组的unique_ptr版本。为了用一个unique_ptr管理动态数组,我们必须在对象类型后面跟一对空方括号:

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

2. allocator类

标准库allocator类定义在头文件memory中,它帮助我们将内存分配和对象构造分离开来。它提供一种类型感知的内存分配方法,它分配的内存时原始的、未构造的。我们需要在此内存中构造对象。

allocator是一个模板。为了定义一个allocator对象,我们必须说明这个allocator可以分配的对象类型。当一个allocator对象分配内存时,它会根据给定的对象类型来确定恰当的内存大小和对其位置:

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

标准库为allocator类定义了两个伴随算法,可以在未初始化内存中创建对象。它们定义在头文件memory中。这些函数返回(递增后的)目的位置迭代器。

  • 28
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值