首页动态正文

C++ 动态数组

new和delete运算符一次分配\释放一个对象,但某系应用需要一次为很多对象分配内存的功能。例如vector和string,当重新分配内存时,他们需要一次性为很多元素分配内存。

C++11和标准库提供了两种一次分配一个对象数组的方法。C++定义了另一种new表达式语法,可以分配并初始化一个对象数组。标准库中包含一个名为allocator的类,允许我们将分配和初始化分离。

当一个应用需要可变数量的对象时,我们在StrBlob中使用vector(或其他标准库容器)总是最简单、安全的方法。


(相关资料图)

后面我们会看到使用标准库容器的优势在C++11的新标准下更为显著。

而且使用容器的类可以使用默认版本的拷贝、赋值和析构函数,而分配动态数组的类必须定义自己的版本。

new和数组

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

方括号中必须是整型,但不必是常量。

也可以用一个表示数组类型的类型别名

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

当我们用上面的方法分配一个数组时,我们并没有得到一个数组类型的对象,而是得到一个数组元素类型的指针。

由于分配的内存并不是一个数组类型,因此不能对动态数组调用begin或end。这些函数使用数组维度来返回首元素和尾后元素的指针。所以我们也不能用范围for语句来处理(所谓的)动态数组中的元素。

注:我们说的动态数组不是数组类型

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

默认下,new分配的对象都是默认初始化的。对数组元素初始化的方法是在大小后面跟一对空括号

C++11新标准下可以用元素初始化器的花括号列表

如果初始化器数目大于元素数目,则new表达式失败,不会分配任何内存。本例中会抛出一个bad_array_new_length异常。

虽然我们用空括号对数组中元素进行值初始化,但不能在括号中给出初始化器,这意味着不能用auto分配数组。

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

虽然我们静态数组不能大小为0,但是我们可以为new分配一个大小为0的数组,这时返回的是一个非空指针。

对于零长度的数组我们可以像使用尾后迭代器一样使用这个指针,只是不能解引用,因为他不指向任何元素

释放动态数组

我们使用一种特殊形式的delete释放动态数组

第二个语句的delete是按逆序销毁pa指向数组的元素,并释放内存。

这里的方括号是必须的,他指示编译器此指针指向一个对象数组的第一个元素,如果没有他(或者在单一对象使用了方括号)都是未定义的。

编译器可能不会给出警告此类行为,所以程序在执行过程中可能在没有警告下行为异常。

智能指针和动态数组

为了用一个unique_ptr管理动态数组,我们必须在对象类型后面跟一对空方括号

int[]指出up指向一个int数组而不是int。由于up指向一个数组,当up销毁管理的指针时会自动使用delete[]。

unique_ptr可以使用下标运算符来访问对象

和unique_ptr不同,shared_ptr不直接支持管理动态数组,如果希望他管理一个动态数组,必须提供自己定义的删除器

如果没有提供删除器,那么代码是未定义的。因为默认下shared_ptr会使用delete销毁他指向的对象,但是此代码是一个动态数组,对其使用delete就会产生释放一个动态数组忘记加[]一样的问题。shared_ptr不直接支持动态数组管理

shared_ptr没有定义下标运算符,而且智能指针类型不支持指针算术运算。所以我们必须用get获得一个内置指针,然后用它访问数组元素。

allocator类

new有一些灵活性上的局限,其中一方面表现在他将内存分配和对象构造绑定(类似的,delete将对象析构和内存释放组合在一起)。这一点在分配单个对象时没有问题,因为我们肯定知道这个对象该是什么值。

当分配一大块内存时,我们通常计划在这块内存上按需构造对象。这时我们想要将内存分配和对象构造分离,这意味着我们会先分配一块内存,而只在需要时构造对象(这里需要一定的开销)。

大部分情况下,将内存分配和对象构造绑定会产生浪费

这里我们不仅可能不需要n个string而且就算对于那些使用的string我们都赋值了两次(一次为默认初始化,一次是赋值)

更重要的是,没有默认构造函数的类不能动态分配数组了。

allocator类

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

他提供一种类型感知的内存分配方法,他分配的内存是未构造的。

allocator也是一个模板。所以我们定义时要指定对象类型,当allocator对象分配内存时,它会根据给定对象类型来确定恰当的内存大小和对齐位置。

allocator分配为构造的内存

allocator分配的内存时未构造的,我们按需要在此内存中构造对象。

标准库中construct成员函数在给定位置构造一个元素。类似make_shared的参数,这些额外参数必须是和构造的对象的类型匹配的合法初始化器

使用原始内存而未定义对象是错误的!

同样我们要销毁每个构造的元素。

同样我们只能对已经构造的对象进行destroy

我们可以将销毁的空间还给系统

我们传递给deallocator的指针不能为空,必须指向allocate分配的内存,而且传递给deallocator的大小参数必须和调用allocate分配内存时提供的大小参数具有一样的值。

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

标准库还未allocator类定义了两个伴随算法,可以在未初始化内存中创建对象,他们也都定义在memory中

类似copy,uninitialized_copy返回目的位置迭代器。因此uninitialized_copy调用会返回一个指针,指向最后一个元素之后的位置。

相关阅读

精彩推荐