C++ Primer学习笔记 - 原始内存分配类allocator

为什么会有allocator类

new将内存分配和对象构造组合到了一起,delete将对象析构和内存释放页组合到了一起。
当申请分配一大块内存时,我们通常希望将内存分配和对象构建分离开。比如,下面将内存和对象构造组合到一起可能导致不必要的浪费:

string *const p = new string[n]; // 申请n个string内存空间,并调用default构造函数构造n个空的string对象
string s;
string *q = p;
while (cin >> s && q != p + n)
    *q++ = s;  // 对*q赋新值
const size_t size = q - p;
// 对数组进行读写等操作
// ...

delete[] p; // 析构数组的每个对象元素,并释放数组空间

可以从上面代码看出,我们对申请的n个string内存空间的每个元素,都构建了2次string对象:一次是使用new申请内存的时候,另外一次是遍历数组对*q赋值的时候。

如何解决这个问题?避免2次对象的构建?
这就需要引入allocator类。

allocator类

头文件<memory>
提供分配原始的、未构造的内存的方法,可以将内存分配和对象构造分离开来。

allocator是一个class template,我们这样定义一个allocator对象并申请分配n个未初始化的string:

allocator<string> alloc; // 定义一个可以分配string的allocator对象
auto const p = alloc.allocate(n); // 分配n个原始string内存空间

allocator常用操作:

allocator<T> a;

a.allocate(n); // 分配一段原始的、未构造的内存空间,该空间为n个原始的T类型对象大小,并返回首地址
a.deallocate(p, n); // 释放allocate分配的空间,首地址p,空间为n个T类型对象大小

a.construct(p, args); // 在p所指向的内存构造一个T对象,p必须是T*类型指针,指向一块原始内存,args是构造T类型对象所需要参数
a.destroy(p); // 析构p所指向内存空间上的T对象,p必须是T*类型指针

allocator构造对象

allocator分配的内存是原始的、未构造的内存空间(称为raw memory),要在其基础上构造对象,就要使用constructor方法构造对象,用destroy方法析构对象。

// 定义allocator对象
allocator<string> alloc; // 定义一个可以分配string的allocator对象
auto const p = alloc.allocate(n); // 分配n个原始string内存空间

// 利用allocator对象,在raw memory上构造string对象
auto q = p;
alloc.construt(q++); // *q为空字符串,并且q指向下一个待构造string对象首地址
alloc.construct(q++, 10, 'c'); // *q为cccccccccc
alloc.construct(q++, "hi"); // *q为hi

// 析构构造的对象
// 析构之后对象便销毁了,内存重新变成raw memory
while (q != p)
    alloc.destory(--q);

// 释放内存空间,归还给系统
// 释放内存后不可再访问
alloc.deallocate(p, n);

注意:deallocate释放内存前,最好先调用destory析构对象,因为构造的对象可能包含其他资源依赖于对象的析构函数释放。

拷贝和填充raw memory的算法

除了可以利用allocator的constructor在raw memory上构造对象,还可以用标准库的2个伴随算法(uninitialized_copy和uninitialized_fill)来对raw memory进行填充。

#include <memory>

uninitialized_copy(b, e, b2); // 从指定迭代器范围[b, e)拷贝所有元素到b2指向的raw memory中。b2指向的memory必须足够大,以容纳输入序列中元素拷贝。返回copy后的目的位置迭代器

uninitialized_copy_n(b, n, b2);  // 从指定迭代器范围[b, b+n)拷贝n个元素到b2指向的raw memory中。返回copy后的目的位置迭代器

unintialized_filled(b, e, t); // 在指定迭代器范围[b, e)中创建对象,所有对象值均为t的拷贝。返回填充后的目的位置迭代器

unintialized_filled_n(b, n, t); // 在指定迭代器范围[b, b+n)中创建n个对象,所有对象值均为t的拷贝。返回填充后的目的位置迭代器

例如,

vector<int> vi = {1,2,3,4,5,6,7,8,9};
allocator<int> alloc;
auto p = alloc.allocate(vi.size() * 2); // 申请vi 2倍大尺寸的raw memory
auto q = unintialized_copy(vi.begin(), vi.end(), p); // 拷贝vi所有元素到p开头的内存块中
unintialized_fill_n(q, vi.size(), 42); // 将p的剩余空间全部填充位42

我们用allocator重新开头new写的那段程序:

/* 原始程序 */
int n = 10;
string *const p = new string[n]; // 申请n个string内存空间,并调用default构造函数构造n个空的string对象
string s;
string *q = p;
while (cin >> s && q != p + n)
    *q++ = s;  // 对*q赋新值
const size_t size = q - p;
// 对数组进行读写等操作
// ...

delete[] p; // 析构数组的每个对象元素,并释放数组空间

/* allocator改写程序 */
int n = 10;
allocator<string> alloc;
auto p = alloc.allocate(n);

string s;
auto q = p;
while (cin >> s && q != p + n)
{
    alloc.construct(q++, s);
}

alloc.deallocate(p, n);
posted @ 2021-12-05 16:01  明明1109  阅读(139)  评论(0编辑  收藏  举报