2010/10/28
关键字:new、delete、allocator
string *sp = new string(“123456”); //new表达式
实际有3个步骤:
1. 调用operator new标准库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象
2. 运行该类型的适当的构造函数,用指定初始化式构造对象
3. 返回新分配并构造的对象的地址
delete sp; //delete表达式
实际有2个步骤:
1. 对sp所指向的对象运行适当的析构函数
2. 通过调用operator delete的标准库函数释放该对象所用的内存
operator new和operator delete函数有两个重载版本,每个版本支持相关的new和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
除了new int、new int[]还有第三种new表达式,定位new(placement new).
new(place_address) type
new(place_address) type (initializer-list)
其中place_address必须是一个指针,而initializer-list提供了(可能为空)初始化列表以便在构造新分配的对象时使用。
例如:
allocator<string> alloc;
string *sp = alloc.allocate(3); //分配3个string的空间
new (sp) string; //直接在sp内存中构造空的string对象
new (sp+1) string("123"); //直接在sp+1内存中构造有初始化值的string对象
alloc.construct(sp+2, string("123")); //构造string对象并拷贝到sp+2位置
如果类定义了自己的成员new和delete,还是可以通过全局作用域操作符调用new、delete表达式:
Type *p = ::new Type;
::delete p;
除了operator new和operator delete,还可以使用allocator类分配和释放未构造的原始内存以及构造和析构对象.
allocator类是一个模板,提供了类型化的内存分配以及对象构造和撤销.而new、delete表达式和标准库函数都是使用void*类型的.
标准allocator类与定制算法 | |
allocator<T> a; | 定义名为a的allocator对象 |
a.allocate(n); | 分配原始内存以保存T类型的n个对象 |
a.deallocate(p, n); | 释放地址p处的n个T类型对象的内存,事先析构这些对象是用户的责任 |
a.construct(p, t); | 在T*指针p所在的内存中构造一个新对象,并调用T类型的拷贝构造函数,用t初始化新对象 |
a.destroy(p); | 调用T*指针p所指对象的析构函数 |
uninitialized_copy(begin, end, begin2); | 从迭代器begin,end指定的输入范围将元素复制到从迭代器begin2开始的未构造的原始内存中.该函数在目的内存构造元素,而不是给它们赋值.用户需要确保begin2指出的目的内存足以保存输入范围中的元素的副本 |
uninitialized_fill(begin, end, t); | 将begin、end指定的范围内原始内存初始化为t的副本,使用拷贝构造函数 |
uninitialized_fill_n(begin, end, t, n); | 将begin、end指定的范围内至多n个T类型对象大小的原始内存初始化为t的副本,范围至少n个T类型对象大小的原始内存,使用拷贝构造函数,同样是用户确保n的合法性 |
对未构造的内存中的对象进行赋值而不是初始化,其行为是未定义的。赋值涉及先删除现存对象,如果没有现存对象赋值操作中的动作会造成灾难性的后果。
allocator类将内存分配和对象构造分开,并且allocate函数分配的内存是连续的.allocate类只是定义了一系列操作内存的接口,本身并不保存内存地址.
可以结合allocator类理解vertor类中的内存是如何分配的。
为了获得可接受的性能,vector预先分配比所需元素更多的空间.每个将元素增加到vector中的成员函数都会检查是否有可用空间容纳当前元素.如果有,该函数在预分配内存中的下一个可用位置初始化一个对象;否则就重新分配vector:vecor获得新空间后,将现存元素复制到新空间,增加新元素并释放旧空间.
#include <memory>
template<class T> class MyVector
{
public:
MyVector():Begin(0),FreeBegin(0),End(0){}
void push_back(const T&);
//...
private:
static std::allocator<T> Alloc;
void Reallocate();
T *Begin;
T *FreeBegin;
T *End;
//...
};
需要解释下三个迭代器的作用:
Begin是内存首地址,FreeBegin是未构造内存的首地址,End是内存末尾.当容器中没有元素时,Begin和FreeBegin指向一处,都指向内存首地址.
容器的size(实际使用的元素的数目)为FreeBegin-Begin.
容器的capacity(在需要重新分配之前,可容纳的元素总量)为End-Begin.
自由空间(在需要重新分配之前,可以增加的元素数目)为End-FreeBegin.
下面是成员函数实现:
template<class T>
void MyVector<T>::push_back(const T& t)
{
if (FreeBegin == End)
Reallocate();
Alloc.construct(FreeBegin, t);
++FreeBegin;
}
template<class T>
void MyVector<T>::Reallocate()
{
std::size_t size = FreeBegin - Begin; //暂时用size_t
std::size_t newCapacity = 2 * max(size, 1); //计算新空间大小
T *pNewBegin = Alloc.allocate(newCapacity); //分配新空间
uninitialized_copy(Begin, FreeBegin, pNewBegin); //拷贝现有元素到新空间
for (T *p = FreeBegin; p!=Begin;) //析构旧空间中的元素
Alloc.destroy(--p);
if (Begin)
Alloc.deallocate(Begin, End-Begin); //释放旧空间
Begin = pNewBegin;
FreeBegin = Begin + size;
End = Begin + newCapacity;
}
算法采用简单却高效的方式,每次重新分配现有内存2倍的空间,如果容器为空,则分配2个元素的空间。