13.5.5 定位new和delete
重载operator new()还有其他两个不常见的用途。
1)我们也许会想在内存的指定位置上放置一个对象。这对于面向硬件的内嵌系统特别重要,在这个系统中,一个对象可能和一个特定的硬件是同义的。
2)我们也许会想在调用new时,能够选择不同的内存分配方案。
这两个特性可以用相同的机制实现:重载的operator new()可以带一个或多个参数。正如前面所看到的,第一个参数总是对象的长度,它在内部计算出来并由编译器传递给new。但其他参数可由我们自己定义:一个放置对象的地址、一个是对内存分配函数或对象的引用,或其他任何使我们方便的设置。
最初在调用过程中传递额外的参数给operator new()的方法看起来似乎有点古怪:在关键字new后是参数表(没有size_t参数,它由编译器处理),参数表后面是正在创建的对象的类名字。例如:
将a作为第二个参数传递给operator new()。当然,这是在operator new()已经声明的情况下才是有效的。
下面的例子显示了如何在一个特定的存储单元里放置一个对象。
注意:operator new()仅返回了传递给它的指针。因此,调用者可以决定将对象存放在哪里,这时在该指针所指向的那块内存上,作为new表达式一部分的构造函数将被调用。
虽然本例只是使用了一个外加的参数,但如果需要实现其他的目的时,使用更多的参数同样也是可以的。
在销毁对象时将会出现两难选择的局面。因为仅有一个版本的operator delete,所以没有办法说“对这个对象使用我的特殊内存释放器”。可以调用析构函数,但不能用动态内存机制释放内存,因为内存不是在堆上分配的。
解决方法是用非常特殊的语法:我们可以显式地调用析构函数。例如:
这里要严重警告一下。因为当某些人想要实时地决定对象的生存时间时,他们使用这种方法在作用范围结束之前的任意时刻销毁对象,而不是调节作用范围或者使用动态对象创建(这样做会更正确)。而如果用这种方法为在栈上创建的对象调用析构函数时,将会出现严重的问题,这是因为析构函数在对象超出作用范围时又会被调用一次。如果为在堆上创建的对象用这种方法调用析构函数,析构函数将被执行,但内存不释放,这是我们所不希望的。用这种方法显式地调用析构函数,其实只有一个原因,即支持operator new()的定位语法。
还有一个定位operator delete,它仅在一个定位operator new表达式的构造函数产生一个异常信息时才被调用(因此该内存在异常处理操作中被自动地清除了)。定位operator delete有一个和定位operator new相对应的参数表,该定位operator new是指在构造函数产生异常信息之前被调用的那一个。这个主题将放在第2卷的异常处理章节中。