条款51:编写 new 和 delete 时需固守常规
1.1 new
C++要求new运算符即使在请求0字节时也返回一个合法的指针。
void* operator new(std::size_t size) { // 你的new操作符可能需要额外的参数using namespace std; if (size == 0) { // 处理0字节请求,size = 1; // 将它们视为1字节请求} while (true) {... //尝试分配size个字节;if (分配成功)return (指向内存的指针);// 分配失败;找出当前的new-handler函数new_handler globalHandler = get_new_handler(0);set_new_handler(globalHandler);if (globalHandler) (*globalHandler)();else throw std::bad_alloc();}
}
1.2 成员函数new
成员函数operator new,会被派生类继承。
class Base {
public:static void* operator new(std::size_t size);...
};
class Derived : public Base // 派生类没有声明new运算符
{...
};
Derived* p = new Derived; // 调用 Base::operator new!
派生类和基类的大小可能不同,处理这种情况的最佳方法是将请求“错误”内存大小的调用丢弃给标准new运算符。
void* Base::operator new(std::size_t size)
{
//sizeof(Base)永远不会是0if (size != sizeof(Base)) // 如果大小是“错误的”,return ::operator new(size); // 采用标准operator new处理请求... // 否则在这里处理请求
}
1.3 operator new[]
实现operator new[](array new)时,唯一能做的就是分配一块原始内存,实际上甚至无法计算这个数组将含有多少个元素(当派生类较大时,(请求的字节数)/sizeof(Base) 并不正确;并且数组还可能包含额外的内容,例如数组大小)。
1.4 delete
对于delete运算符,事情就简单多了。你需要记住的是,C++保证delete空指针总是安全的,所以你需要遵守这个保证。
void operator delete(void* rawMemory) noexcept
{if (rawMemory == 0) return; // 如果空指针正在被删除,什么都不做... // 释放rawMemory指向的内存;
}
1.5 成员函数delete
成员函数版本也很简单,只需要将大小“错误”的delete行为转交给标准的operator delete:
class Base { // 和之前一样,但现在声明了delete运算符
public: static void* operator new(std::size_t size);static void operator delete(void* rawMemory, std::size_t size) noexcept;...
};
void Base::operator delete(void* rawMemory, std::size_t size) noexcept
{if (rawMemory == 0) return; // 检查空指针if (size != sizeof(Base)) { // 如果大小是“错误的”,使用标准delete运算符::operator delete(rawMemory); return; }... // 释放rawMemory指向的内存;return;//如果被释放的对象派生自某个缺少虚析构函数的基类,那么 C++ 传给operator delete的size可能不正确
}
1.6 总结
- new运算符应该包含一个尝试分配内存的无限循环,如果它不能满足内存请求,应该调用new-handler,应该处理0字节的请求,类专用的版本应该处理比预期更大的块的请求。
- 如果传递给delete运算符的指针是null,那么它什么也不会做。类专用版本应该处理比预期更大的块。