34、C++ Primer 4th笔记,特殊工具与技术,优化内存分配(1)
时间:2011-05-22 来源:edwardlewiswe
1、C++的内存分配是一种类型操作:new为特定类型分配内存,并在新分配的内存中构造该类型的一个对象。new表达式自动运行合适的构造函数来初始化动态分配的类类型对象。
2、在每种情况下(预先分配内存以保存用户级(user-level objects)对象或者保存类的内部数据)都需要将内存分配与对象构造分离开。
3、对未构造的内存中的对象进行赋值而不是初始化,其行为是未定义的。对许多类而言,这样做引起运行时崩溃。
4、C++提供下面两种方法分配和释放未构造的原始内存。
1)allocator 类,它提供可感知类型(type-aware)的内存分配。这个类支持一个抽象接口,以分配内存并随后使用该内存保存对象。
2)标准库中的 operator new 和 operator delete函数,它们分配和释放需要大小的原始的、未类型化的内存。
5、C++ 还提供不同的方法在原始内存中构造和撤销对象。
1)allocator 类定义了名为 construct 和 destroy 的成员:construct 成员在未构造内存中初始化对象,destroy 成员在对象上运行适当的析构函数。
2)定位 new 表达式(placement new expression)接受指向未构造内存的指针,并在该空间中初始化一个对象或一个数组。
3)可以直接调用对象的析构函数来撤销对象。运行析构函数并不释放对象所在的内存。
4)算法 uninitialized_fill 和 uninitialized_copy 像 fill 和 copy算法一样执行,除了它们在目的地构造对象而不是给对象赋值之外。
现代 C++ 程序一般应该使用 allocator 类来分配内存,它更安全更灵活。但是,在构造对象的时候,用 new 表达式比allocator::construct 成员更灵活。有几种情况下必须使用new。
6、allocator 类
allocator类是一个模板,它提供类型化的内存分配以及对象构造与撤销。
allocator<T> a; |
定义名为a的allocator对象,可以分配内存或构造T类型的对象 |
a.allocate(n) |
分配原始的未构造内存以保存 T 类型的 n 个对象 |
a.deallocate(p, n) |
Deallocates memory that held n objects of type T starting at address contained in the T* pointer named p . It is the user's responsibility to run destroy on any objects that were constructed in this memory before calling deallocate . |
a.construct(p, t) |
在 T* 指针 p 所指内存中构造一个新元素。运行 T 类型的复制构造函数用 t 初始化该对象 |
a.destroy(p) |
运行 T* 指针 p 所指对象的析构函数 |
uninitialized_copy(b, e, b2) |
从迭代器 b 和 e 指出的输入范围将元素复制到从迭代器b2 开始的未构造的原始内存(unconstructed, raw memory)中。该函数在目的地构造元素,而不是给它们赋值。假定由 b2 指出的目的地足以保存输入范围中元素的副本 |
uninitialized_fill(b, e, t) |
将由迭代器 b 和 e 指出的范围中的对象初始化为 t 的副本。假定该范围是未构造的原始内存。使用复制构造函数构造对象 |
uninitialized_fill_n(b, e, t, n) |
将由迭代器 b 和 e 指出的范围中至多 n 个对象初始化为t 的副本。假定范围至少为n个元素大小。使用复制构造函数构造对象 |
allocator类将内存分配和对象构造分开。当allocator对象分配内存的时候,它分配适当大小并进行内存对齐(aligned)来保存给定类型对象的空间。但是,它分配的内存是未构造的,allocator的用户必须分别construct和destroy放置在该内存中的对象。
1)使用allocator管理类成员数据
示例代码
#include "iostream" #include "string" #include "vector" using namespace std; template <class T> class MyVector { public: MyVector():elements(0), first_free(0), end(0){} void Push_back(const T&); //... private: static std::allocator<T> alloc; //object to get raw memory void reallocate(); //get more space and copy existing elements. T* elements; //pointer to first element in the array. T* first_free; //pointer to first free element in the array T* end; //pointer to one past the end of the array. //... } template <class T> void MyVector<T>::Push_back(const T& t) { if (first_free == end) reallocate(); //get more space and copy existing elements. alloc.construct(first_free, t); ++first_free; } template <class T> void MyVector<T>::reallocate() { //compute size of current array and allocate space for twice as many elements std::ptrdiff_t size = first_free - elements; std::ptrdiff_t newcapacity = 2 * max(size, 1); //allocate space to hold newcapacity number of elements of type T T* newelements = alloc.allocate(newcapacity); //construct copies of the existing elements in the new space uninitialized_copy(elements, first_free, newelements); //destory the old elements in reverse older for (T *p = first_free; p != elements; /*empty*/) alloc.destroy(--p); //deallocate cannot be called on a 0 pointer if (elements) //return the memory that held the elements alloc.deallocate(elements, end - elements); //make our data structure point to the new elements elements = newelements; first_free = elements + size; end = elements + newcapacity; }
7、operator new函数和operator delete函数
// new expression
string * sp = new string("initialized");
的时候,实际上发生三个步骤。首先,该表达式调用名为 operator new 的标准库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象;接下来,运行该类型的一个构造函数,用指定初始化式构造对象;最后,返回指向新分配并构造的对象的指针。
当使用 delete 表达式
delete sp;
删除动态分配对象的时候,发生两个步骤。首先,对 sp 指向的对象运行适当的析构函数;然后,通过调用名为 operator delete 的标准库函数释放该对象所用内存。
我们不能重定义new和delete表达式的行为。
1)operator new与operator delete接口
void *operator new(size_t); // allocate an object
void *operator new[](size_t); // allocate an array
void *operator delete(void*); // free an object
void *operator delete[](void*); // free an array
2)使用分配操作符函数
虽然 operator new 和 operator delete 函数的设计意图是供 new 表达式使用,但它们通常是标准库中的可用函数。可以使用它们获得未构造内存,它们有点类似 allocate 类的 allocator 和 deallocate 成员。
使用示例
T* newelements = alloc.allocate(newcapacity); //上式表达与下式表达等价 T* newelements = static_cast<T*>(operator new[](newcapacity * sizeof(T))); if (elements) //return the memory that held the elements alloc.deallocate(elements, end - elements); // //上式表达与下式表达等价 operator delete[](elements);
注意:使用allocator与operator new有一个重要的区别就是:operator new在void*指针而不是类型化的指针上进行操作。一般而言,使用allocator比直接使用operator new与operator delete函数更为类型安全。
allocate成员分配类型化的内存,所以使用它的程序可以不必计算以字节为单位的所需内存量,它们也可以避免对 operator new 的返回值进行强制类型转换。类似地,deallocate释放特定类型的内存,也不必转换为void*。
8、定位new表达式
标准库函数 operator new 和 operator delete 是 allocator 的allocate 和 deallocate 成员的低级版本,它们都分配但不初始化内存。
There are also lower-level alternatives to the allocator members construct and destroy . Thesemembers initialize and destroy
objects in space allocated by an allocator object.
类似于construct成员,第三种new表达式,称为定位new(placement new)。定位new表达式在已分配的原始内存中初始化一个对象,它与new的其他版本的不同在于:它不分配内存,相反,它接受指向已分配但未构造内存的指针,并在该内存中初始化一个对象。实际上,定位new表达式使我们能够在特定的、预分配的内存地址构造一个对象。
new (place_address) type
new (place_address) type (initializer-list)
其中 place_address 必须是一个指针,而 initializer-list 提供了(可能为空的)初始化列表,以便在构造新分配的对象时使用。
使用示例
alloc.construct(first_free, t); //上式等价于下式 //copy t into element addressed by first_free new(first_free) T(t);
定位new表达式比allocator类的construct成员更灵活。定位new表达式初始化一个对象的时候,它可以使用任何构造函数,并直接建立对象。construct函数总是使用复制构造函数。
示例代码
allocator<string> alloc; string *sp = alloc.allocate(2); //allocate space to hold 2 strings //two ways to construct a string from a pair of iterators new (sp) string(b, e); // construct directly in place alloc.construct(sp + 1, string(b, e)); //build and copy a temporary