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,指向数组最后一个元素。
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条