原文链接: 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. 理解 LSTM 网络

    递归神经网络 人类并不是每时每刻都从头开始思考.正如你阅读这篇文章的时候,你是在理解前面词语的基础上来理解每个词.你不会丢弃所有已知的信息而从头开始思考.你的思想具有持续性. 传统的神经网络不能做到这 ...

  2. [leetcode] 230. Kth Smallest Element in a BST 找出二叉搜索树中的第k小的元素

    题目大意 https://leetcode.com/problems/kth-smallest-element-in-a-bst/description/ 230. Kth Smallest Elem ...

  3. C++ 多线程中使用cout还是printf

    在多线程的设计模式下,如果多个线程都使用cout打印信息,那么很容易出现内容交替的现象,例如下图: 代码如下: 如果把cout替换成printf,那么就不会出现这个问题,运行结果如下图: 对应代码如下 ...

  4. magento关联产品

    <?php $related= $_product->getRelatedProducts(); foreach($related as $_item): $_item = $_item- ...

  5. valgrind- 内存泄漏-how to install and use

    1.how to install my host computer is ARM, U need to Attention yours... valgrind下载: http://valgrind.o ...

  6. zookeeper 入门指导

      zookeeper数据模型 zookeeper有一个层级命名空间,和一个分布式文件系统非常相似 .唯一的不同是每个节点可以有关联的数据,子节点也是.就像有一个文件系统,并且允许文件可以是一个目录. ...

  7. 浏览器通过http协议通过nginx访问ftp服务器上的文件

    1.修改nginx配置文件 2.修改nginx默认目录为ftp默认目录 3.修改成自定义路径后,并修改此目录第一行,修改为root或是nginx用户 4.重新加载 cd /usr/local/ngin ...

  8. 不缓存AJAX

    最好的办法是:写上这段代码 $.ajaxSetup({cache:false}); 这样页面中,所有的ajax请求,都执行这个,就不必改已经完成的N个接口 最初接受的办法是:在url后面添加当前时间 ...

  9. php生成迷宫和迷宫寻址算法实例

    较之前的终于有所改善.生成迷宫的算法和寻址算法其实是一样.只是一个用了遍历一个用了递归.参考了网上的Mike Gold的算法. <?php //zairwolf z@cot8.com heade ...

  10. php curl伪造来源ip和refer实例代码

    php curl伪造来源ip和来路refer实例代码1: //随机IP function Rand_IP(){ $ip2id= round(rand(600000, 2550000) / 10000) ...