原文链接: http://blog.csdn.net/Xiejingfa/article/details/50955295

今天我们来讲讲C++的allocator类。


C++提供了new和delete操作符来管理动态内存空间。new操作通常需要完成两部分工作:一是在系统中申请内存空间,二是在分配的内存上构造对象。delete操作也通常需要完成对应的两部分工作:一个调用相应的析构函数销毁对象,二是回收内存。从这点看,new和delete操作符把内存空间的分配回收与对象的构建销毁紧紧关联在一起。对于new和delete的这种特性,如果我们只是申请单个对象的时候倒是很合适。因为我们几乎可以确定单个对象一定会被使用同时我们也希望将单个对象的内存和初始化组合在一起。

那如果我们需要分配一大块内存呢?这种情形就有点不同。考虑下面这段代码:

#include <iostream>
using namespace std; class Example
{
public:
Example() { cout << "example default constructor..." << endl; }
Example(int x) : a(x) { cout << "example constructor..." << endl; }
~Example() { cout << "example destructor..." << endl; }
int a;
}; int main()
{
// 如果Example没有默认构造函数,则无法动态分配数组
Example *p = new Example[10];
delete[] p;
}

在上面这段代码中,我们new了一个长度为10的Example数组,这时new操作符不仅申请了相应大小的内存空间,还在分别执行了每个元素的默认构造函数。所以它的输出如下:

example default constructor...
example default constructor...
example default constructor...
example default constructor...
example default constructor...
example default constructor...
example default constructor...
example default constructor...
example default constructor...
example default constructor...
example destructor...
example destructor...
example destructor...
example destructor...
example destructor...
example destructor...
example destructor...
example destructor...
example destructor...
example destructor...

如果我们new了长度为10的数组,但并没有全部使用呢?这样剩下那些没有使用的元素就这就产生了额外的对象构造的成本。可见,将内存分配和对象构造结合在一起可能会导致不必要的浪费。而且如果Example中没有默认的构造函数,我们也无法new出动态数组。

有没有一种方法可以将内存的分配回收和对象的构造销毁分离开,让我们在一块内存空间中按需分配对象呢?这就是我们今天要介绍的allocator类。

allocator类是一个模板类,定义在头文件memory中,用于内存的分配、释放、管理,它帮助我们将内存分配和对象构造分离开来。具体地说,allocator类将内存的分配和对象的构造解耦,分别用allocate和construct两个函数完成,同样将内存的释放和对象的析构销毁解耦,分别用deallocate和destroy函数完成。下面我们就来介绍一下这几种函数。

1、内存分配和对象构造

我们可以使用下面语句定义一个名为alloc为类型T分配内存的allocator对象:

allocator<T> alloc;

有了allocator对象后,我们可以使用下面的函数让系统为我们分配一段原始的、未构造的、可以保持n个类型为T的对象的内存空间:

alloc.allocate(n)

与new操作类似,allocate函数调用成功后返回一个指向该段内存第一个元素的指针。allocator分配的对象是未构造的,我们在使用前需要调用construct函数在此内存中构造对象,如果使用为构造的内存,其行为是未定义的。construct的使用方法如下:

alloc.construct(p, args)

p必须是一个类型为T*的指针,指向一块由allocator分配的未构造内存空间。arg为类型T构造函数的参数,用来在p指向的内存空间中构造一个T类型对象。

示例:

#include <iostream>
#include <memory>
using namespace std; class Example
{
public:
Example() : a(0) { cout << "example default constructor..." << endl; }
Example(int x) : a(x) { cout << "example constructor..." << endl; }
~Example() { cout << "example destructor..." << endl; }
int a;
}; int main()
{
allocator<Example> alloc;
Example *p = alloc.allocate(2);
alloc.construct(p);
alloc.construct(p + 1, 3);
cout << p->a << endl;
p++;
cout << p->a << endl;
}

在上面这段代码中,我们先申请了可以保存两个Example对象的内存空间,然后在第一个位置上调用Example的默认构造函数来构造对象,在第二个位置调用另一个构造函数来构造对象。输出如下:

example default constructor...
example constructor...
0
3

从输出中我们看到,我们只是完成了内存分配和对象构造的工作,构造好的对象并没有析构,内存也没有回收。下面我们来看看如何使用allocator类来完成对象析构和内存回收工作。

2、对象析构和内存回收

当我们使用完对象后,需要对每个构造的对象调用destroy函数来销毁它们。

alloc.destroy(p)

destroy函数接受一个T *的指针,对其指向对象执行构造函数。对象被析构销毁后,分配好的内存空间依然存在,我们可以重新在这块内存上继续构造对象,重复利用,也可以对该内存进行回收操作,归还给系统。内存释放用deallocate函数完成:

alloc.deallocate(p, n)

deallocate函数释放从p开始的长度为n的内存空间,其中p是allocate的返回值,n是该段内存 保存的元素个数,应和allocate的参数n保持一致。需要注意的是,必须由用户来保证调用deallocate前对每个在这块内存中创建的对象调用destroy函数。

示例:

class Example
{
public:
Example() : a(0) { cout << "example default constructor..." << endl; }
Example(int x) : a(x) { cout << "example constructor..." << endl; }
~Example() { cout << "example destructor..." << endl; }
int a;
}; int main()
{
allocator<Example> alloc;
Example *p = alloc.allocate(2);
alloc.construct(p);
alloc.construct(p + 1, 3);
cout << p->a << endl;
cout << (p + 1)->a << endl;
alloc.destroy(p);
alloc.destroy(p + 1);
alloc.deallocate(p, 2); }

输出如下:

example default constructor...
example constructor...
0
3
example destructor...
example destructor...

总结:

  1. allocator类将内存分配回收和对象构造析构分离开来,可以让我们先分配内存再按需构造。
  2. allocator类分配的内存是未构造的,为了使用已经分配好的内存,我们必须使用construct构造对象。如果使用未构造的内存,其行为是未定义的。
  3. 只能对真正构造了的对象进行destroy操作,用户必须保证在调用deallocate函数回收内存前对这块内存上的每个元素调用destroy函数。

【C++11新特性】 - 空间配置allocator类的更多相关文章

  1. C++ 11 新特性

    C++11新特性:          1.auto          2.nullptr          3.for          4.lambda表达式          5.override ...

  2. [转载] C++11新特性

    C++11标准发布已有一段时间了, 维基百科上有对C++11新标准的变化和C++11新特性介绍的文章. 我是一名C++程序员,非常想了解一下C++11. 英文版的维基百科看起来非常费劲,而中文版维基百 ...

  3. 在C++98基础上学习C++11新特性

    自己一直用的是C++98规范来编程,对于C++11只闻其名却没用过其特性.近期因为工作的需要,需要掌握C++11的一些特性,所以查阅了一些C++11资料.因为自己有C++98的基础,所以从C++98过 ...

  4. c++学习书籍推荐《深入理解C++11 C++11新特性解析与应用》下载

    百度云及其他网盘下载地址:点我 编辑推荐 <深入理解C++11:C++11新特性解析与应用>编辑推荐:C++标准委员会成员和IBM XL编译器中国开发团队共同撰写,权威性毋庸置疑.系统.深 ...

  5. C++ 11学习和掌握 ——《深入理解C++ 11:C++11新特性解析和应用》读书笔记(一)

    因为偶然的机会,在图书馆看到<深入理解C++ 11:C++11新特性解析和应用>这本书,大致扫下,受益匪浅,就果断借出来,对于其中的部分内容进行详读并亲自编程测试相关代码,也就有了整理写出 ...

  6. C++11新特性总结 (二)

    1. 范围for语句 C++11 引入了一种更为简单的for语句,这种for语句可以很方便的遍历容器或其他序列的所有元素 vector<int> vec = {1,2,3,4,5,6}; ...

  7. C++11新特性总结 (一)

    1. 概述 最近在看C++ Primer5 刚好看到一半,总结一下C++11里面确实加了很多新东西,如果没有任何了解,别说自己写了,看别人写的代码估计都会有些吃力.C++ Primer5是学习C++1 ...

  8. C++11新特性——range for

    很多编程语言都有range for语法功能,自C++11起,终于将这个重要功能加入C++标准中.range for语句,可以方便的遍历给定序列中的每个元素并对其执行某种操作. 1.基本语法 for(d ...

  9. C++11新特性——大括号初始化

    C++11之前,C++主要有以下几种初始化方式: //小括号初始化 string str("hello"); //等号初始化 string str="hello" ...

随机推荐

  1. MovingBoxes左右滑动放大图片插件

    MovingBoxes左右滑动放大图片插件在产品预览时很有用哦 实例代码 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transition ...

  2. ModuleNotFoundError: No module named '_tkinter'

    https://blog.csdn.net/blueheart20/article/details/78763208 apt search python3-tk apt install python3 ...

  3. IOS沙盒机制

    一,ios应用程序只能在为该程序创建的文件系统中读取文件,不可以去其他地方访问,此区域被称为沙盒 1,每个应用程序都有自己的存储空间 2,应用程序不能翻过自己的围墙去访问别的存储空间的内容. 3,应用 ...

  4. LUN挂载到Linux主机后,如何对磁盘进行分区

    将阵列上的LUN挂载到Linux主机后,如何对磁盘进行分区,方法参考https://www.ibm.com/developerworks/cn/linux/l-lpic1-v3-104-1/ fdis ...

  5. 关于Gson无法将匿名类转化为json字符串的问题

    在使用gson过程中,一般会将数据存在一个对象模型中,使用gson将模型转换成json字符串用于数据交互. 代码形如: ArrayList<String> list = new Array ...

  6. [LeetCode&Python] Problem 682. Baseball Game

    You're now a baseball game point recorder. Given a list of strings, each string can be one of the 4 ...

  7. [LeetCode&Python] Problem 463. Island Perimeter

    You are given a map in form of a two-dimensional integer grid where 1 represents land and 0 represen ...

  8. JSON-java

    import net.sf.json.JSONArray; import net.sf.json.JSONObject; JSONObject jsonObject1 = new JSONObject ...

  9. SysRq魔法键的使用

    SysRq魔法键的使用 1.SysRq简介它能够在系统处于极端环境时响应按键并完成相应的处理.这在大多数时候有用.SysRq 经常被称为 Magic System Request,它被定义为一系列按键 ...

  10. C51 头文件中的 extern

    C51 头文件使用 extern 的目的是声明外部变量或函数. 使用注意: 只放在  .h 文件中. 声明时不用赋值. extern 只是声明不是定义.