温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

再学C++ Primer(12)- C++中的高级内存管理

发布时间:2020-07-29 18:44:15 来源:网络 阅读:480 作者:拳四郎 栏目:开发技术

C++ primer的最后一章,虽说是高级主题,但其实是一些非常有用的东西,包括内存分配,RTTI,volatile等等。


new/delete

C++中内存方面最常用的就是new表达式和delete表达式。

string *sp = new string("fucku");

实际上发生了三件事:

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

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

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


delete sp;

实际上发生了两件事:

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

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


C++中还有一些更加“原子”的做法.

下面的两种方法来分配和释放未构造的原始内存

1)allocator类,能够针对某个类型进行内存分配,该类支持一个抽象接口,以分配内存并随后使用该内存保存对象;

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


下面的四种方法还提供不同的方法在原始内存中构造和撤销对象

1)allocator类定义名为construct和 destroy的成员,construct成员在未构造内存中初始化对象,destroy成员在对象上运行适当的析构函数;

2)placement new 表达式,接受指向未构造内存的指针,并在该空间初始化一个对象或是一个数组;

3)可以直接调用对象的析构函数来删除对象。运行析构函数并不释放对象所占有的内存;

4)使用uninitialized_fill,uninitialized_fill_n 和uninitialized_copy,进行构造或者拷贝构造。


Vector类的部分实现

vector.h

template<typename T> class Vector{ public:     Vector():elements(0),first_free(0),end(0){}     void push_back(const T&);     //... private: 	static std::allocator<T> alloc; 	void reallocate(); 	T* elements; 	T* first_free; 	T* end; 	//... }

私有成员的三个指针说明一下:

elements,指向数组第一个元素;

first_free,指向最后一个实际元素之后的元素;

end,指向数组最后一个元素。

再学C++ Primer(12)- C++中的高级内存管理


Vector的size等于 first_free - elements;

Vector的capacity等于end - elements +1;

剩余的自由空间是 end - first_free.


还有两个函数,push_back 用于vector中push元素,reallocate用于重新分配内存。

push_back 函数的实现如下

template <typename T> void Vector<T>::push_back(const T& t) { 	if(first_free == end) 		reallocate(); 	alloc.construct(first_free,t); 	++first_free; }

首先检查一下是否有剩余空间,如果没有空间的话调用reallocate函数重新分配内存,接下来在first_free所指向的内存块构建对象;最后将first_free指针向后移动一个单位。


高潮来了,reallocate函数!

先说明一下内存扩张策略:每次重新分配时分配两倍内存,函数首先计算当前在用的元素数目,将该数目翻倍,并请求allocator对象来获得所需数量的空间,如果Vector为空,就分配两个元素。

这样的策略平摊下来的时间复杂度是O (1).实现如下:

template <typename T> void Vector<T>::reallocate() { 	std::ptrdiff_t size  = first_free - elemens; 	std::ptrdiff_t newcapacity = a * max(size,1); 	 	T* newelements = alloc.allocate(newcapaciy); 	 	uninitialized_copy(elements, first_free, newelements); 	for(T *p = first_free; p != elements;) 		alloc.destroy(--p); 		 	if(elements) 		alloc.deallocate(elements, end-elements); 		 	elements = newelements; 	firts_free = elements + size; 	end = elements + newcapacity; }

uninitialized_copy 使用标准copy算法的特殊版本,这个版本在原始的未构造的内存中复制构造每一个元素;

for循环对旧数组中每个对象调用allocator的destroy成员,逆序销毁元素,destroy调用T的析构函数来释放对象的资源;

一旦复制并析构了元素,就释放原来占用的空间,不过在deallocate之前,斌需检查elements的合法性;

最后,并需重置指针以指向新分配并初始化的数组。


重载new/delete

       虽然C++标准库已经为我们提供了new与delete操作符的标准实现,但是由于缺乏对具体对象的具体分析,系统默认提供的分配器在时间和空间两方面都存在着一些问题:分配器速度较慢,而且在分配小型对象时空间浪费比较严重,特别是在一些对效率或内存有较大限制的特殊应用中。比如说在嵌入式的系统中,由于内存限制,频繁地进行不定大小的内存动态分配很可能会引起严重问题,甚至出现堆破碎的风险;再比如在游戏设计中,效率绝对是一个必须要考虑的问题,而标准new与delete操作符的实现却存在着天生的效率缺陷。此时,我们可以求助于new与delete操作符的重载,它们给程序带来更灵活的内存分配控制。除了改善效率,重载new与delete还可能存在以下两点原因:

检测代码中的内存错误。
获得内存使用的统计数据。

        相对于其他的操作符,operator new具有一定的特殊性,在多个方面上与它们大不相同。首先,对于用户自定义类型,如果不重载,其他操作符是无法使用的,而operator new则不然,即使不重载,亦可用于用户自定义类型。其次,在参数方面,重载其他操作符时参数的个数必须是固定的,而operator new的参数个数却可以是任意的,只需要保证第一个参数为size_t类型,返回类型为void *类型即可。所以operator new的重载会给我们一种错觉:它更像是一个函数重载,而不是一个操作符重载。


详细请参考:effective C++ 33条

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI