C++内存分配与对象构造的分离
在C++中,我们基本用new(delete)操作符分配(释放)内存。new操作符为特定类型分配内存,并在新分配的内存中构造该类型的一个对象。new表达式自动运行合适的构造函数来初始化每个动态分配的类类型对象。即new表达式既分配了内存同时也构造了对象。
然而,我们一定会遇到这样的情况:预先分配用于创建新对象的内存,需要时在预先分配的内存中构造每个对象。即将内存分配与对象构造分开进行,这样做的理由是:
(1)在内存分配时构造对象很浪费,可能会创建从不使用的对象。
(2)当实际使用预先分配的对象时,被使用的对象很可能要重赋新值。
string* pstr = new string[5];
上面举了个不合适的例子(当然你应该用vector<string>来代替),毫无疑问被分配的5个string空间是被string默认构造函数初始化了,而且接下来你肯定得对pstr[0...4]重新赋值。所以new操作符这种分配特点会增加运行时开销。尤其是某些用户的类类型要求对象分配更快一些,做法通常是:预先分配用于创建新对象的内存,需要时在预先分配的内存中构造每个新对象。
一、分配原始内存
C++提供两种方法分配和释放未构造的原始内存:
(1)allocator类,它提供可感知类型的内存分配。这个类支持抽象接口,以分配内存并随后使用该内存保存对象。
(2)标准库中的operator new和operator delete,它们分配和释放需要大小的原始的,未类型化的内存。
1、allocator类
allocator类是一个模板,它提供类型化的内存分配以及对象构造与撤销。它支持的操作如下:
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:指向数组本身之后的那个元素。看下面这张图可能更清楚一点。
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 = 2 * max(size, 1);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(2); new (sp) string(b, e);
注意:
(1)定位new表达式初始化一个对象时,可使用任何构造函数,并直接建立对象。allocator类的construct成员总是使用拷贝构造函数。
(2)对于值类型而言,直接构造对象与构造临时对象并进行拷贝没有什么区别,性能差别基本没什么意义。但对某些类而言,使用拷贝构造函数是不可能的(拷贝构造函数可能是私有的等),或应该避免的。这种情况,或许你应该考虑定位new表达式。
转载于:https://www.cnblogs.com/mengwang024/p/4408841.html
C++内存分配与对象构造的分离相关推荐
- JVM学习笔记之-堆,年轻代与老年代,对象分配过程,Minor GC、Major GC、Full GC,堆内存大小与OOM,堆空间分代,内存分配策略,对象分配内存,小结堆空间,逃逸分析,常用调优工具
堆的核心概述 概述 一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域.Java堆区在JVM 启动的时候即被创建,其空间大小也就确定了.是JVM管理的最大一块内存空间. 堆内存的大小是可 ...
- 对象创建方法,对象的内存分配,对象的访问定位
对象创建方法: JVM遇到一条new指令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载.连接和初始化过. 如果没有,那必须先执行相应的类的加 ...
- jvm学习笔记(3)——java对象的内存分配和对象的回收(GC)
引言: 之前的文章已经提过,java对象实例是存放在堆上的,至于是在伊甸区.存活区还是老年区,这些都是从对象回收(GC)角度来进行的逻辑划分.所以我们先说对象的回收(GC),然后再依据GC的策略来说明 ...
- jvm内存分配及对象创建和回收过程
个人博客:https://suveng.github.io/blog/ Java历史 2004.9 jdk1.5 tiger 自动装箱拆箱,泛型,,注解,枚举,变长参数,增强for循环 ...
- java对象的内存分配_java对象在内存的分配问题
今天看到一个不错的PPT:Build Memory-efficient Java Applications,开篇便提出了一个问题,在Hotspot JVM中,32位机器下,Integer对象的大小是i ...
- C++ 的 allocator类 提供类型化的内存分配以及对象的分配和撤销
在没有深入的叙述本文之前先来几个引子 C++中的allocator 提到了rebind,而boost库中也有一个boost::bind 慢慢琢磨吧.留个记忆体.以后还会看这些东西的 memory po ...
- 内存分配(allocator)
一,allocator类 new有一些灵活性上的局限,其中一方面体现在它将内存分配和对象构造组合在一起.类似的,delete将对象析构和内存释放组合在一起.当分配单个对象时,通常希望将内存分配和对象初 ...
- 动态内存分配与智能指针
内存分配: 静态存储区: 局部static对象 类的static数据成员 定义在任何函数之外的变量 栈区: 函数内的非static对象 动态内存分配的方式有: new和delete 智能指针(shar ...
- java对象的内存分配流程
了解对象的内存分配流程对常见内存溢出问题.jvm优化有很大作用. 内存分配原则 对象栈内分配 通常理解new对象都在堆中分配存储空间,但是当(通过逃逸分析 确定)对象仅在方法内使用而未被外部访问的时候 ...
最新文章
- 这5个 AI 用例,转变了传统商务沟通
- Python改变生活 | OCR识别的花样使用
- AMD/NV大跌2周后仅两天收复失地 挖矿成背后推手
- Sqli-labs less 50
- java doc 编写
- string来存放二进制数据
- 最全面的_Redis_基本操作总结
- 【vulnhub】靶机- [DC系列]DC9(附靶机))
- 【Nginx】通过反向代理配置本地图床功能
- A - 数据结构实验之栈与队列一:进制转换
- ProxySQL Getting started
- javascript异步延时加载及判断是否已加载js/css文件
- PSASP7.61版本,求新能源的算例
- 158.导出northwind中employees的图像
- 电影级视频剪辑PR调色/FCP调色/达芬奇调色预设LUTs
- 智能手机成瘾者的大脑功能和结构的改变
- OFD格式文件怎么转PDF格式?分享一个轻松转换小妙招
- SpringBoot教程(十一) | SpringBoot集成Mybatis
- 02-JavaScript变量的强制类型转换
- 用latex在写MDPI的文章文献References部分时遇到的问题(! Misplaced alignment tab character .)