- 当operator new抛出异常以反映一个未满足的内存需求之前,它会先调用一个客户指定的错误处理函数,一个所谓的set-new-handler
new_handler是个typedef,定义出一个指针指向函数,该函数没有参数也没有返回值。set_new_handler则是“获得一个new_handler并返回一个new_handler”的函数。namespace std{ typedef void(*new_handler)(); new_handler set_new_handler(new_handler p) throw(); }
可以这样用set_new_handler:
如果想支持class专属之new_handler,可以令每一个class提供自己的set_new_handler和operator new即可,其中set_new_handler使客户得以指定class的专属new_handler,至于operator new则确保在分配class对象内存的过程中以class专属之new_handler替换global new_handlervoid outOfMem(){ std::cerr<<"unable to satisfy request for memory"; std::abort(); } int main() { std::set_new_handler(outOfMem); int* pBigDataArray=new int[1000000000000]; }
- 齐位意义重大,因为C++要求所有operator new返回的指针都有适当的对齐。malloc就是在这样的要求下工作,所以令operator new返回一个得自malloc的指针是安全的。如果返回一个得自malloc且偏移一个int大小的指针,没人能保证它的安全
-
我们没有办法直接取得new_handler函数指针,所以必须调用set_new_handler找出它来void* operator new(std::size_t size) throw(std::bad_alloc) { using namespace std; if(size==0) size=1; while(true) { 尝试分配size bytes if(分配成功) return (一个指针,指向分配得来的内存); //分配失败;找出目前的new-handling函数 new_handler globalHandler=set_new_handler(0); set_new_handler(globalHandler); if(globalHandler) (*globalHandler)(); else throw std::bad_alloc(); } }
while是个无穷循环,退出循环的唯一办法是:内存被分配成功或new_handlering函数做了一件事情:让更多内存可用、安装另一个new_handler、卸除new_handler、抛出bad_alloc异常(或其派生物)、或是承认失败而直接return。现在,对于new_handler为什么必须做出上述其中某些事情应该很清楚了,因为不那么做operator new内的while循环永远不会结束
如果发生了继承,会发生分配大小错误
处理此情势的最佳做法是将“内存申请量错误”的调用行为改采用标准operator new,像这样:class Base{ static void* operator new(std::size_t size) throw(std::bad_alloc); ... } class Derived:public Base {...}; Derived* p=new Derived; //这里调用的是Base::operator new
void* Base::operator new(std::size_t size) throw(std::bad_alloc) { using namespace std; if(size!==sizeof(Base)) return ::operator new(size); ... }
- 重写operator delete需要记住的唯一事情就是C++保证“删除null指针永远安全”,所以应该这样写:
void operator delete(void* rawMemory) throw() { if(rawMemory==0) return; 现在,归还rawMemory所指的内存 } ``` ```javascript void* Base::operator delete(void* rawMemory,std::size_t size) throw() { using namespace std; if(rawMemory==0) return; if(size!==sizeof(Base)) return ::operator delete(rawMemory); 现在,归还rawMemory所指的内存 ... } ```
- 写了placement new也要写placement delete
如果operator new接受的参数除了一定会有的那个size_t之外还有其他,这便是个所谓的placement new。如果一个带额外参数的operator new没有“带相同额外参数”的对应版本的operator delete,那么当new的内存分配动作需要取消并恢复旧观时就没有任何operator delete会被调用,会发生内存泄漏(分配内存时的operator new调用成功,类类型的构造函数抛出异常的情况下)
placement delete只有在“伴随placement new调用而触发的构造函数”出现异常时才会被调用。对着一个指针施行delete绝不会导致调用placement delete
如果对所有与placement new相关的内存泄漏宣战,我们必须同时提供一个正常的operator delete(用于构造期间无任何异常被抛出)和一个placement版本(用于构造期间有异常被抛出)delete pw; //调用正常的operator delete
- 由于成员函数的名称会掩盖其外围作用域中的相同名称,即使参数列表不同,所以需要小心避免让class专属的news掩盖客户期望的其他news(包括正常版本)。假设一个base class,其中声明唯一一个placement operator new,客户端会发现他们无法使用正常形式的new:
class Base{ public: ... static void* operator new(std::size_t size,std::ostream& logStream) throw(std::bad_alloc); //这个new会掩盖正常的形式 ... }; Base* pb=new Base; //错误,因为正常形式的operator new被掩盖 Base* pb=new (std::cerr) Base; //正确,调用Base的placement new ``` 同样,derived classes中的operator new会掩盖global版本和继承而来的operator new版本
Effective C++笔记 —— 第八章
最新推荐文章于 2021-11-17 08:45:26 发布