第一部分
这个C++ FAQ条目解释了为什么有人想要为自己的类重载new
和delete
运算符。本FAQ将尝试以符合标准的方式解释如何实现这样的重载。
实现自定义的new
运算符
C++标准(§18.4.1.1)将operator new
定义为:
void* operator new (std::size_t size) throw (std::bad_alloc);
C++标准在§3.7.3和§18.4.1中规定了这些运算符的自定义版本必须遵守的语义。
让我们总结一下要求。
要求1:它应该动态分配至少size字节的内存,并返回指向分配内存的指针。引用C++标准第3.7.4.1.3节的内容:
“分配函数尝试分配所请求的存储空间。如果成功,它将返回一个指向起始地址为所请求大小的存储块的指针...”
标准进一步规定:
“...返回的指针应适当对齐,以便可以将其转换为任何完整对象类型的指针,然后用于访问分配的存储中的对象或数组(直到通过调用相应的释放函数显式释放存储)。即使请求的空间大小为零,请求也可能失败。如果请求成功,返回的值应为非空指针值(4.10),与之前返回的值p1不同,除非该值p1随后被传递给operator delete。”
这给我们带来了进一步的重要要求:
要求 #2:我们使用的内存分配函数(通常是malloc()
或其他自定义分配器)应该返回一个适当对齐的指针,该指针可以转换为完整对象类型的指针,并用于访问该对象。
要求 #3:即使请求的字节数为零,我们的自定义操作符new
也必须返回一个合法的指针。
其中一个明显的要求甚至可以从new
的原型中推断出:
要求 #4:如果new
无法分配所请求大小的动态内存,则应抛出std::bad_alloc
类型的异常。
但是!这还不止于表面所见:如果你仔细查看new
运算符的文档(标准的引用在下面),它声明:
如果使用了
set_new_handler来定义一个
new_handler函数,那么当标准的默认
operator new
无法分配所需的存储空间时,会调用这个
new_handler
函数。
为了理解我们自定义的
new
如何支持这个要求,我们需要了解以下内容:
什么是
new_handler
和
set_new_handler
?
new_handler
是一个指向接受并返回空值的函数的指针类型,而
set_new_handler
是一个接受并返回
new_handler
的函数。
set_new_handler
的参数是一个指向函数的指针,如果无法分配请求的内存,则
operator new
应该调用该函数。它的返回值是先前注册的处理程序函数的指针,如果没有先前的处理程序,则返回null。
下面是一个代码示例,以便更清楚地说明问题:
#include <iostream>
#include <cstdlib>
void outOfMemHandler()
{
std::cerr << "Unable to satisfy request for memory\n";
std::abort();
}
int main()
{
std::set_new_handler(outOfMemHandler);
int *pBigDataArray = new int[100000000L];
return 0;
}
在上面的例子中,
operator new
(很可能)无法为100,000,000个整数分配空间,函数
outOfMemHandler()
将被调用,程序将在
发出错误消息后中止。
在这里需要注意的是,当
operator new
无法满足内存请求时,它会重复调用
new-handler
函数,直到它可以找到足够的内存或没有更多的新处理程序。在上面的例子中,除非我们调用
std::abort()
,否则
outOfMemHandler()
将会被
反复调用。因此,处理程序应该确保下一次分配成功,或注册另一个处理程序,或不注册处理程序,或者不返回(即终止程序)。如果没有新的处理程序并且分配失败,操作员将抛出异常。
继续 1
C++ FAQ
不是为所有自问自答的书籍式问答形式所准备的,这是普通用户可以想到的。 - Lightness Races in Orbit