上面的程序提示输入5个姓名,然后显示它们。程序中定义了名为Names的类,它的构造函数初始化对象的name值。这个类定义了自己的new和delete运算符。这是因为程序能保证不会一次使用超过maxnames个姓名,所以可以通过重载默认的new和delete运算符来提高运行速度。
Names类中的内存池是一个字符数组,可以同时容纳程序需要的所有姓名。与之相关的布尔型数组inuse为每个姓名记录了一个true和false值,指出内存中的对应的项是否正在使用。
重载的new运算符在内存池中寻找一个没有被使用的项,然后返回它的地址。重载的delete运算符则标记那些没有被使用的项。
在类定义中重载的new和delete运算符函数始终是静态的,并且没有和对象相关的this指针。这是因为编译器会在调用构造函数之前调用new函数,在调用析构函数后调用delete函数。
new函数是在类的构造函数之前被调用的。因为这时内存中还不存在类的对象而且构造函数也没有提供任何初始化值,所以它不可以访问类的任何成员。同理,delete运算符是在析构函数之后被调用的,所以它也不可以访问类的成员。
四、异常监测和异常处理
1.检测异常
上面的例子还缺少必要的保护机制。比如,重载的delete运算符函数并没有检查它的参数,确认其是否落在内存池内部。如果你绝对相信自己编的程序中不会传递错误的指针值给delete运算符,那么可以省掉合法性检查以提高效率,特别是在优先考虑效率的程序中。否则应该使用预编译的条件语句。在软件的测试版本中加入这些检测,在正式的发行版本中去掉这些检查。
2.重载new和delete中的异常处理
上面的两个重载运算符函数都是用了异常处理。异常处理是C++(www.cppentry.com)的新内容之一,目前还没有讲到。在这里不必关心它是如何工作的。上面程序中,当试图分配超过内存池容量的Names缓冲区,重载的new运算符函数就会抛出异常,终止程序。
五、重载new[]和delete[]
对于上面的程序,假如有下面的语句:
Names *nms=new Names
...
delete [] nms;
那么,这些语句会调用全局new和delete运算符,而不是重载过的new和delete。为了重载能为对象数组分配内存的new和delete运算符,必须像下面的程序一样,对new[]和delete[]也进行重载。
#include \"iostream.h\"
#include \"string.h\"
#include \"stddef.h\"
#include \"new.h\"
const int maxnames = 5;
class Names
{
char name[25];
static char Names::pool[];
static bool Names::inuse[maxnames];
public:
Names(char* s) { strncpy(name,s,sizeof(name)); }
void* operator new(size_t) throw(bad_alloc);
void operator delete(void*) throw();
void display() const { cout<<name<<endl; }
};
char Names::pool[maxnames * sizeof(Names)];
bool Names::inuse[maxnames];
void* Names::operator new[](size_t size) throw(bad_alloc)
{
int elements=size/sizeof(Names);
int p=-1;
int i=0;
while((i<maxnames) && (p==-1))
{
if(!inuse[i]) p=i;
++i;
}
// Not enough room.
if ((p==-1) || ((maxnames-p)<elements)) throw bad_alloc();
for(int x=0; x<elements; ++x) inuse[p+x]=elements;
return pool+p*sizeof(Names);
}
void Names::operator delete[](void* b) throw()
{
if(b!=0)
{
int p=((char*)b- pool)/sizeof(Names);
int elements=inuse[p];
for (int i=0; i<elements; i++) inuse[p+i]=0;
}
}
int main()
{
Names* np = new Names[maxnames];
int i;
for(i=0; i<maxnames; i++)
{
cout<<endl<<\"Enter name # \"<<i+1<< \":\";
char name[25];
cin >> name;
*(np + i) = name;
}
for(i=0; i<maxnames; i++) (np+i)->display();
delete [] np;
return 0;
}
重载new[]和delete[]要比重载new和delete考虑更多的问题。这是因为new[]运算符时为数组分配内存,所以它必须记住数组的大小,重载的delete[]运算符才能正确地把缓冲区释放回内存池。上面的程序采用的方法比较简单,吧原来存放缓冲区使用标志的布尔型数组换成一个整型数组,该数组的每个元素记录new[]运算符分配的缓冲区个数,而不再是一个简单的true。当delete[]运算符函数需要把缓冲区释放回内存池时,它就会用该数组来确认释放的缓冲区个数。