C++的内存分配是一种类型化操作:new为特定类型分配内存,并在新分配的内存中构造该类型的一个对象。new表达式自动运行合适的构造函数来初始化每个动态分配的类类型对象。
new基于每个对象分配内存的事实可能会对某些类强加不可接受的运行时开销,这样的类可能需要使用用户级的类类型对象分配能够更快一些。这样的类使用的通用策略是,预先分配用于创建新对象的内存,需要时在预先分配的内存中构造每个新对象。另外一些类希望按最小尺寸为自己的数据成员分配需要的内存。
在每种情况下(预先分配内存以保存用户级对象或者保存类的内部数据)都需要将内存分配与对象构造分离开。将内存分配与对象构造分离开的明显的理由是,在预先分配的内存中构造对象很浪费,可能会创建从不使用的对象。当实际使用预先分配的对象的时候,被使用的对象必须重新赋以新值。更微妙的是,如果预先分配的内存必须被构造,某些类就不能使用它。例如,考虑vector,它使用了预先分配策略。如果必须构造预先分配的内存中的对象,就不能有基类型为没有默认构造函数的vector——vector没有办法知道怎样构造这些对象。
1、C++中的内存分配
C++中,内存分配和对象构造紧密纠缠,就像对象和内存回收一样。使用new表达式的时候,分配内存,并在该内存中构造一个对象;使用delete表达式的时候,调用析构函数撤销对象,并将对象所用内存返还给系统。
接管内存分配时,必须处理这两个任务。分配原始内存时,必须在该内存中构造对象;在释放该内存之前,必须保证适当地撤销这些对象。
注:对未构造的内存中的对象进行赋值而不是初始化,其行为是未定义的。对许多类而言,这样做引起运行时崩溃。赋值涉及删除现存对象,如果没有现存对象,赋值操作符中的动作就会有灾难性效果。
C++提供以下两种方法分配和释放未构造的原始内存:
1)allocator类,它提供可感知类型的内存分配。这个类支持一个抽象接口,以分配内存并随后使用该内存保存对象。
2)标准库中的operatornew 和 operatordelete,它们分配和释放需要大小的原始的、未类型化的内存。
C++还提供不同的方法在原始内存中构造和撤销对象。
1)allocator类定义了名为construct和 destroy的成员,其操作正如它们的名字所指出的那样:construct成员在未构造内存中初始化对象,destroy 成员在对象上运行适当的析构函数。
2)定位new表达式接受指向未构造内存的指针,并在该空间中初始化一个对象或一个数组。
3)可以直接调用对象的析构函数来撤销对象。运行析构函数并不释放对象所在的内存。
4)算法uninitialized_fill和uninitialized_copy像 fill和 copy 算法一样执行,除了它们的在目的地构造对象而不是给对象赋值之外。
2、allocator类
allocator类是一个模板,它提供类型化的内存分配以及对象构造与撤销。
allocator类将内存分配和对象构造分开。当allocator对象分配内存的时候,它分配适当大小并排列成保存给定类型对象的空间。但是,它分配的内存是未构造的,allocator的用户必须分别construct和 destroy放置在该内存中的对象。
3、operator new函数和operator delete 函数
使用new表达式:string* sp =new string(“initialized”);
实际发生3个步骤:
1)调用名为operator new的标准库函数。分配足够大的原始的未类型化的内存,以保存指定类型的对象。
2)运行该类型的一个构造函数,用指定初始化式构造对象。
3)返回指向新分配并构造对象的指针。
(总结:分配内存,构造对象,返回对象指针)
使用delete操作符delete sp;删除对象时,发生两个步骤:
1)对指向的对象运行适当的析构函数。
2)调用名为operator delete的标准库函数释放该对象的内存。
(总结:析构对象,释放空间)
注:new 表达式与 operator new 函数的区别(是两回事):
1)不能重定义 new 和 delete 表达式的行为;
2)new 表达式获通过调用 operator new 函数得内存,并接着在该内存中构造一个对象;通过执行 delete 表达式撤销一个对象,并接着调用 operator delete 函数,以释放该对象使用的内存。
operator new和operator delete有两个不同的版本。每个版本支持相关的new表达式和delete表达式。
void *operator new(size_t); //allocate an object;
vodi *operator new [](size_t); //allocate an array;
void operator delete(void*); //free an object;
void operator delete[](void*); //free an array;
虽然operator new和operator delete的设计意图是供new和delete表达式使用,但是我们仍然可以使用它们获得未构造的内存。这有点类似allocator的allocate和deallocate成员。
一般来说,使用allocator比直接使用operator new 和operator delete 函数更为类型安全。
4、定位new表达式(placement new)
标准库函数 operator new 和 operator delete 是 allocator 的allocate 和 deallocate 成员的低级版本,它们都分配但不初始化内存。
定位 new 表达式在已分配的原始内存中初始化一个对象,它与 new 的其他版本的不同之处在于,它不分配内存。相反,它接受指向已分配但未构造内存的指针,并在该内存中初始化一个对象。实际上,定位 new 表达式使我们能够在特定的、预分配的内存地址构造一个对象。
定位new表达式形式:
new (place-address) type; //在指针p处构造type类型对象;
new (place-address) type(initializer list); //在指针p处用初始化列表构造对象;
其中 place_address 必须是一个指针,而 initializer-list 提供了(可能为空的)初始化列表,以便在构造新分配的对象时使用。
使用定位new表达式比使用allocator类的construct成员更灵活。定位new在初始化一个对象的时候,它可以使用任何构造函数,并直接建立对象,而construct函数总是使用复制构造函数。
5、显示析构函数的调用
我们可以使用析构函数的显式调用作为调用 destroy 函数的低级选择。显式调用析构函数的效果是适当地清除对象本身。但是,并没有释放对象所占的内存,如果需要,可以重用该内存空间。
对于使用定位new表达式构造对象的程序 ,显示调用析构函数:
for (T* p=first_free;p!=elements;/*empty*/) p->~T(); //call the destructor;