C++内存分配与对象构造的分离

时间:2022-09-23 07:43:24

在C++中,我们基本用new(delete)操作符分配(释放)内存。new操作符为特定类型分配内存,并在新分配的内存中构造该类型的一个对象。new表达式自动运行合适的构造函数来初始化每个动态分配的类类型对象。即new表达式既分配了内存同时也构造了对象。

然而,我们一定会遇到这样的情况:预先分配用于创建新对象的内存,需要时在预先分配的内存中构造每个对象。即将内存分配与对象构造分开进行,这样做的理由是:

(1)在内存分配时构造对象很浪费,可能会创建从不使用的对象。

(2)当实际使用预先分配的对象时,被使用的对象很可能要重赋新值。

string* pstr = new string[]; 

上面举了个不合适的例子(当然你应该用vector<string>来代替),毫无疑问被分配的5个string空间是被string默认构造函数初始化了,而且接下来你肯定得对pstr[0...4]重新赋值。所以new操作符这种分配特点会增加运行时开销。尤其是某些用户的类类型要求对象分配更快一些,做法通常是:预先分配用于创建新对象的内存,需要时在预先分配的内存中构造每个新对象。

一、分配原始内存

C++提供两种方法分配和释放未构造的原始内存:

(1)allocator类,它提供可感知类型的内存分配。这个类支持抽象接口,以分配内存并随后使用该内存保存对象。

2)标准库中的operator new和operator delete,它们分配和释放需要大小的原始的,未类型化的内存。

1、allocator类

allocator类是一个模板,它提供类型化的内存分配以及对象构造与撤销。它支持的操作如下:

C++内存分配与对象构造的分离

allocator类将内存分配和对象构造分开。当allocator对象分配内存的时,它分配适当大小并排列成保存给定类型对象的空间。它分配的内存是未被构造的,allocator的用户必须分别construct和destroy放置在该内存中的对象。

vector的自增长告诉我们:vector为了实现快速内存分配,其实际分配的空间要比当前需要的空间多一些。(实际空间因库的实现不同而不同),下面为了说明allocator的使用,我们简陋地实现STL vector中的push_back操作。

template <class T> class VECTOR
{
public:
VECTOR() : elements(NULL), first_free(NULL), end(NULL){}
void push_back(const T&);
private:
static allocator<T> alloc;
void reallocate();
T *elements;
T *first_free;
T *end;
};

elements:指向数组的第一个元素;first_free:指向最后一个实际元素之后的那个元素;end:指向数组本身之后的那个元素。看下面这张图可能更清楚一点。

C++内存分配与对象构造的分离

template <class T> void VECTOR<T>::push_back(const T& t)
{
if (first_free == end) //确认是否有可用空间
{
reallocate(); //分配新空间并复制现存元素
} alloc.construct(first_free, t); //构造新元素
++first_free;
}

下面是reallocate()的简单实现:

template <class T> void VECTOR<T>::reallocate()
{
ptrdiff_t size = first_free - elements;
ptrdiff_t newCapacity = * max(size, ); T *newElement = alloc.allocate(newCapacity); //分配两倍内存 uninitialized_copy(elements, first_free, newElement); //原内存元素拷贝到新内存 for (T *p = first_free; p != elements; ) //原内存元素逆序调用析构函数
{
alloc.destroy(--p);
} if (elements)
{
alloc.deallocate(elements, end - elements); //撤销原内存空间
} elements = newElement; //调整新内存空间指针指向
first_free = elements + size;
end = elements + newCapacity;
}

说明:本例只做简单说明。如果你对vector或STL实现感兴趣,可以拜读《STL源码分析》这本书,我也从这本书学到很多知识。

2、operator new函数和operator delete函数

当执行string *sp = new string("initialized");时发生三个步骤:

(1)调用名为operator new的标准库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象。

(2)运行该类型的一个构造函数,用指定初始化式构造对象。

(3)返回指向新分配并构造的对象的指针。

当执行delete sp;时发生两个步骤:

(1)对sp指向的对象运行适当的析构函数。

(2)调用名为operator delete的标准库函数释放该对象所用内存。

operator new和operator delete函数有两个重载版本,每个版本支持相关的new操作:

void *operator new(size_t);

void *operator new[](size_t);

void *operator delete(size_t);

void *operator delete[](size_t);

说明:虽然operator new和operator delete的设计意图是供new操作符使用,但它们也是标准库中的函数,可使用它们获得未构造的内存。举例如下:

    T *newElement = alloc.allocate(newCapacity);                //分配两倍内存
T *newElement = static_cast<T*>(operator new[](sizeof(T) * newCapacity));

上面两条语句是等价的,下面这两条语句也是等价的。

    alloc.deallocate(elements, end - elements);                //撤销原内存空间
operator delete[](elements);

说明:allocator类分配类型化的内存,使用时不必计算以字节为单位所需的内存,也避免对operator new的返回值进行强制类型转换。比直接使用operator new,operator delete更为安全。

二、对象构造和撤销

C++提供了不同方法在原始内存中构造和撤销对象:

(1)allocator类的成员construct和destroy。

(2)定位new表达式。

(3)直接调用对象的析构函数撤销对象。撤销对象并不释放对象所在的内存。

(4)算法uninitialized_copy和uninitialized_fill构造对象。

下面主要介绍定位new表达式(其他情况我们都见过了)。

定位new表达式在已分配的原始内存中初始化一个对象,它不分配内存,接受指向已分配但未构造内存的指针,并在该内存中初始化一个对象。定位new表达式的形式是:

new (place_address) type

new (place_address) type(initializer-list)

其中place_address必须为指针,initializer-list提供了一个可能为空的初始化列表。举例如下:

alloc.construct(first_free, t);
new (first_free) T(t); string *sp = alloc.allocate();
new (sp) string(b, e);

注意:

(1)定位new表达式初始化一个对象时,可使用任何构造函数,并直接建立对象。allocator类的construct成员总是使用拷贝构造函数。

(2)对于值类型而言,直接构造对象与构造临时对象并进行拷贝没有什么区别,性能差别基本没什么意义。但对某些类而言,使用拷贝构造函数是不可能的(拷贝构造函数可能是私有的等),或应该避免的。这种情况,或许你应该考虑定位new表达式。

C++内存分配与对象构造的分离的更多相关文章

  1. Java虚拟机创建对象的内存分配以及对象的内存布局

    本博文知识参考周志明<深入理解Java虚拟机> Java虚拟机在创建对象使如果进行内存分配: 1.指针碰撞 2.空闲列表 Java在多线程情况下创建对象的内存分配: Java完成对象内存分 ...

  2. C&plus;&plus;内存分配与释放

    C++内存分配与释放 1. new 运算符 与 operator new一条 new 表达式语句( new Type; )中的 new 是指 new 运算符.operator new 是定义在 #in ...

  3. C&plus;&plus; Primer 学习笔记&lowbar;98&lowbar;特殊的工具和技术 --优化内存分配

    特殊的工具和技术 --优化内存分配 引言: C++的内存分配是一种类型化操作:new为特定类型分配内存,并在新分配的内存中构造该类型的一个对象.new表达式自己主动执行合适的构造函数来初始化每一个动态 ...

  4. 阿里面试官:小伙子,你给我说一下JVM对象创建与内存分配机制吧

    内存分配机制   逐步分析 类加载检查: 虚拟机遇到一条new指令(new关键字.对象的克隆.对象的序列化等)时,会先去检查这个指令的参数在常量池中定位到一个类的符号引用,并且这个符号引用代表的类是否 ...

  5. JVM的艺术-对象创建与内存分配机制深度剖析

    JVM的艺术-对象创建与内存分配机制深度剖析 引言 本章将介绍jvm的对象创建与内存分配.彻底带你了解jvm的创建过程以及内存分配的原理和区域,以及包含的内容. 对象的创建 类加载的过程 固定的类加载 ...

  6. Java内存分配和内存管理

    首先是概念层面的几个问题: Java中运行时内存结构有哪几种? Java中为什么要设计堆栈分离? Java多线程中是如何实现数据共享的? Java反射的基础是什么? 然后是运用层面: 引用类型变量和对 ...

  7. java内存分配详细论

    P.S. 想写这篇总结酝酿了有个来月了,却始终感觉还差点什么东西,一直未敢动笔. 最近两天连夜奋战,重新整理下前面查阅的资料.笔记,还是决定将它写出来. 现在提出几个问题,如果都能熟练回答的大虾,请您 ...

  8. C&plus;&plus;中的内存分配

    C++提供下面两种方法分配和释放未构造的原始内存 (1)allocator 类,它提供可感知类型的内存分配 (2)标准库中的 operator new 和 operator delete,它们分配和释 ...

  9. C&plus;&plus;指针和动态内存分配

    指针和动态内存分配 数组与指针 数组 数组名是一个指针常量. 数组名传递数据时,传递的是地址. 数组作为函数参数时不指定第一维大小. 对象数组 A a[2] = {A(1,2)}; 执行时先调用有参数 ...

随机推荐

  1. 记录rewrite url我之前不知道的地方

    大部分url重写的需求是伪静态,当然有很多第三方开源组件,但是这种需求的核心方法其实就是context.rewritePath() 要是系统像ARR那样,用重写做代理和反向代理,一般的重写就不行了,c ...

  2. JavaScript中原型和原型链

    原型[prototype]: 为其他对象提供共享属性的对象. 每个函数都有一个原型(prototype)属性,这个属性是一个指针,指向一个对象,这个对象包含特定实例共享的一些属性和方法. 以例服人: ...

  3. JQuery 表格 隔行换色 和鼠标滑过的样式

    $(document).ready(function () { $(".Pub_TB tbody tr:even td").css("background-color&q ...

  4. 1093&period; Count PAT&&num;39&semi;s &lpar;25&rpar;

    The string APPAPT contains two PAT's as substrings. The first one is formed by the 2nd, the 4th, and ...

  5. 使用Vitamio打造自己的Android万能播放器(6)——在线播放(播放列表)

    前言 新版本的VPlayer由设计转入开发阶段,预计开发周期为一个月,这也意味着新版本的Vitamio将随之发布,开发者们可以和本系列文章一样,先开发其他功能.本章内容为"在线视频播放列表& ...

  6. BZOJ 1026&colon; &lbrack;SCOI2009&rsqb;windy数&lpar; dp &rpar;

    dp..dp(x, t) 表示共x位, 第x位为t有多少个windy数. 对答案差分, 我们只需统计1 ~ l-1和1 ~ r的windy数数量. 考虑如何计算[1, n]的答案 : 从最高位到最低位 ...

  7. Verilog中的标点

    在Verilog中有时候会误用的上引号 1,define 中的 `define INITIAL  0 这个单引号用的是键盘左上角的那个单引号,其实就是一个小撇. 2,4'd0 这个 用的是才是叫真正的 ...

  8. R 网络图 nodes&comma;edges属性计算

    前面提到了用R画网络图,免不了要对网络图nodes和edges的特征做一些统计.分享下我的代码: ########## nodes edges的统计########### # ####nodes的度有 ...

  9. go 的匿名函数和闭包

    匿名函数 匿名函数是指不需要定义函数名的一种函数实现方式. 在Go语言中,函数可以像普通变量一样被传递或使用,这与C语言的回调函数比较类似.不同的是,Go语言支持随时在代码里定义匿名函数. 匿名函数由 ...

  10. 转: IOS程序内发短信 MFMessageComposeViewController

    文章转载地址:http://www.headsky.org/?p=63 iOS4.0新加入了MFMessageComposeViewController和MFMessageComposeViewCon ...