C++内存分配与对象构造的分离(一)

2015-07-20 17:06:49 ? 作者: ? 浏览: 6
C++中,我们基本用new(delete)操作符分配(释放)内存。new操作符为特定类型分配内存,并在新分配的内存中构造该类型的一个对象。new表达式自动运行合适的构造函数来初始化每个动态分配的类类型对象。即new表达式既分配了内存同时也构造了对象。
?
然而,我们一定会遇到这样的情况:预先分配用于创建新对象的内存,需要时在预先分配的内存中构造每个对象。即将内存分配与对象构造分开进行,这样做的理由是:
?
(1)在内存分配时构造对象很浪费,可能会创建从不使用的对象。
?
(2)当实际使用预先分配的对象时,被使用的对象很可能要重赋新值。
?
string* pstr = new string[5];?
上面举了个不合适的例子(当然你应该用vector来代替),毫无疑问被分配的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 VECTOR
{
public:
? ? VECTOR() : elements(NULL), first_free(NULL), end(NULL){}
? ? void push_back(const T&);
private:
? ? static allocator alloc;
? ? void reallocate();
? ? T *elements;
? ? T *first_free;
? ? T *end;
};
?
elements:指向数组的第一个元素;first_free:指向最后一个实际元素之后的那个元素;end:指向数组本身之后的那个元素。看下面这张图可能更清楚一点。
?
?
?
?
template void VECTOR::push_back(const T& t)
{
? ? if (first_free == end) ? ? ? ? ? ? ? ? ? ?//确认是否有可用空间
? ? {
? ? ? ? reallocate(); ? ? ? ? ? ? ? ? ? ? ? ?//分配新空间并复制现存元素
? ? }
?
? ? alloc.construct(first_free, t); ? ? ? ? ? ?//构造新元素
? ? ++first_free;
}
?
下面是reallocate()的简单实现:
?
?
template void VECTOR::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(operator new[](size
-->

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: