Effective c++ 8 49...52
手工管理内存 heap资源
软件使用内存的行为特征---手动定/修改分配和归还内存的工作。
C++内存管理例程的行为?
operator new, operator delete, +new-handler, operator new[], operator delete[]
注:不负责STL容器的HEAP管理
多线程环境下的内存管理?
heap——可被改动的全局性资源。race condition(竞速状态)出现 大家都同时访问一个资源,只拼速度,不稳定。
可改动的static数据。thread-aware(线程感知)程序员 同步控制(synchronization),无锁算法 ,精心防止并发访问(concurrent access)?
49*了解new-handle的行为
operator new无法满足某一种内存分配需求时,会怎么做呢?
1、调用一个客户指定的错误处理函数——一个new-handler。
2、抛出异常bad_alloc(【旧】返回一个null指针)。
客户如何指定的?
#ifdef _M_CEE_PURE typedef void (__clrcall * new_handler) (); #else typedef void (__cdecl * new_handler) ();//new_handler是没有参数也不返回任何东西的函数指针 #endif _CRTIMP2 new_handler __cdecl set_new_handler(_In_opt_ new_handler) _THROW0(); // establish alternate new handler//获得一个new_handler并返回一个new_handler的函数。参数指向operater new无法分配足够内存时应该调用的new-handler函数。返回被替换的那个new-handler函数。 _STD_END <new>头文件
客户定义的错误处理函数new handler应该什么样子?
举例:
void outOfMen()//没有参数也不返回任何值的函数 { std::cerr<<"Unable to satisfy request for memory\n";//提示 //1让更多内存可用——程序开始执行就分配一块内存,当new-handler第一次被调用,将它们释还给程序使用。 //2调用std::set_new_handler呼叫另一个new_handler函数。或者直接修改自身,比如改static数据,全局数据,namespace数据。 //3抛出bad_alloc异常(或派生),异常不会被operatornew捕捉,会被传播到内存索求处。 //4不返回,调用abort或者exit终止程序。 std::abort();//终止程序 } int main() { std::set_new_handler(outOfMen);//登录处理函数 int* pBigDataArray = new int[1000000000L]; return 0; }
第二个话题:实现类自己的new_handler:new类对象的时候,出问题时,调用本类自己的new_handler。
技术思路:Widget类的operator new操作
1、 把Widget的处理函数登录
2、 把 登录返回值——旧的处理函数,存起来(资源方式)
3、 分配内存
4、 不论正确与否,结束operator new操作之前,(资源类析构函数完成)把旧的处理函数登录,恢复全局之前的状态。
//*******.h******** #include <iostream> class Widget{ public: static std::new_handler set_new_handler(std::new_handler p) throw(); static void* operator new(std::size_t size) throw(std::bad_alloc); private: static std::new_handler currentHandler; }; class NewHandlerHolder//2 把登录返回值——旧的处理函数,存起来(资源方式) { public: explicit NewHandlerHolder(std::new_handler nh ):handler(nh){} ~NewHandlerHolder(){std::set_new_handler(handler);}//4 不论正确与否,结束operator new操作之前,(资源类析构函数完成)把旧的处理函数登录,恢复全局之前的状态。 private: std::new_handler handler; //记录handler NewHandlerHolder(const NewHandlerHolder&); //阻止copying NewHandlerHolder operator=(const NewHandlerHolder&); }; //*******.cpp******** #include "template1.h" std::new_handler Widget::currentHandler = 0; std::new_handler Widget::set_new_handler( std::new_handler p ) throw() { std::new_handler oldHandler = currentHandler; currentHandler = p;//1把Widget的处理函数登录 return oldHandler; } void* Widget::operator new(std::size_t size) throw(std::bad_alloc) { NewHandlerHolder h(std::set_new_handler(currentHandler));//1把Widget的处理函数登录//2 把登录返回值——旧的处理函数,存起来(资源方式)
//所谓资源方式:RAII,创建对象即赋值,撤销即销毁。
return ::operator new(size);//3分配内存 } //*******main******** #include "template1.h" void outOfMem() { std::cerr<<"Widget outOfMen"; std::abort(); } int main() { Widget::set_new_handler(outOfMem); Widget* pw = new Widget; return 0; }
第三个话题:什么类都可以用这套技术的。复用。
1、mixin base class 实现第二个话题中功能的类作为模板类,基类。事实上那个T并未在定义类的函数中使用。用处请看2。
template<typename T> class NewHandlerSupport{ //【NewHandlerSupport<T>】 public: static std::new_handler set_new_handler(std::new_handler p) throw(); static void* operator new(std::size_t size) throw(std::bad_alloc); //……其他的operator new版本 private: static std::new_handler currentHandler; }; class NewHandlerHolder//hold old 资源 【未改变】 { public: explicit NewHandlerHolder(std::new_handler nh ):handler(nh){} ~NewHandlerHolder(){std::set_new_handler(handler);} private: std::new_handler handler; //记录handler NewHandlerHolder(const NewHandlerHolder&); //阻止copying NewHandlerHolder operator=(const NewHandlerHolder&); }; //*******.cpp******* 【NewHandlerSupport<T>】 template<typename T> std::new_handler NewHandlerSupport<T>::currentHandler = 0; template<typename T> std::new_handler NewHandlerSupport<T>::set_new_handler( std::new_handler p ) throw() { std::new_handler oldHandler = currentHandler; currentHandler = p; return oldHandler; } template<typename T> void* NewHandlerSupport<T>::operator new(std::size_t size) throw(std::bad_alloc) { NewHandlerHolder h(std::set_new_handler(currentHandler)); return ::operator new(size); }
2.CRTP (怪异循环模板 curiously recurring template pattern)——Do It For Me
class Widget:public NewHandlerSupport<Widget>//每一个T一个static 的currentHandler【目的】 { //和先前一样,但不必声明set_new_handler和operator new,已经继承来了。 //【目的】继承自NewHandlerSupport<Widget>使得Widget类拥有自己的static 的currentHandler。 };
50*什么时候需要手工管理内存?
见书中列举。
一个好的内存管理器。 Booth的Pool程序库——分配大量小型对象。
一个小例子:
static const int signature = 0xDEADBEEF; typedef unsigned char Byte; //手工operator new 实现内存前后有标志 void* operator new( std::size_t size) { using namespace std; size_t realSize = size + 2*sizeof(int);//用户所需内存再增加我们两个标志的内存 void* pMem = malloc(realSize);//malloc保证对齐安全 if (!pMem) throw bad_alloc(); //将signature写入内存的最前和最后段落 *( static_cast<int*>(pMem) )= signature;//最前面一个int的内存给了signature用 *( reinterpret_cast<int*>( static_cast<Byte*>(pMem) +realSize-sizeof(int) ) )= signature;//最后面一个int的内存给了signature用 //返回指针 指向前面sinature后内存位置 return static_cast<Byte*>(pMem) + sizeof(int); //不确定对齐,不安全 }
51*编写operator new和operator delete需要固守的常规。
1、operator new 中应包含一个无限循环,并在其中尝试分配内存。如果成功返回指针,如果失败调用new-handler函数或者抛出异常。
2、operator new 应有能力处理0byte申请。
void* operator new( std::size_t size ) { using namespace std; if ( size == 0 )//2、operator new 应有能力处理0byte申请 { size = 1; } while (true)//1、operator new 中应包含一个无限循环,并在其中尝试分配内存。如果成功返回指针,如果失败调用new-handler函数或者抛出异常。 { 尝试分配size byte; if (分配成功) return(一个指针,指向分配的来的内存); //分配失败:找出目前的new-handler函数 new_handler globalHandler = set_new_handler(0); set_new_handler(globalHandler); if (globalHandler)//有new-handler函数 (*globalHandler)() else//没有 throw std::bad_alloc(); } }
3、Class专属版本operator new 应处理“比正确大小更大的(错误)申请”
void* BaseClass::operator new(std::size_t size) { if (size != sizeof(BaseClass))//如果大小错误 return ::operator new(size);//起用标准new实现 ……//否则这里实现 }
写operator new[]时要做的唯一的事:分配一块raw memeory(未加工内存)。
4、operator delete 应在收到null指针时不做任何事。“C++保证删除null指针永远安全”
1 void* operator delete(void* rawMemory) 2 { 3 if (rawMemory ==0 ) return;//如果将被删除的是个null指针,什么都不做 4 //否则这里归还rawMemory所指内存 5 }
5、Class专属版本operator delete应处理“比正确大小更大的(错误)申请”
1 void* BaseClass::operator delete(void* rawMemory,std::size_t size)//比常规的多一个size参数 2 { 3 if (rawMemory ==0 ) return; 4 if (size != sizeof(BaseClass)){//如果大小错误 5 ::operator delete(rawMemory);//起用标准delete实现 6 return; 7 } 8 ……//这里实现归还rawMemory内存 9 return; 10 }
52*placement new和placement delete
placement new是什么:
是除了size那个参数之外,还有其它参数的operator new或者特指其它参数就是void*的operator new。
问题出在:
当调用完new紧接着调用的构造函数抛出异常时,客户代码中的delete无力释放这个new因为不知道指针,C++运行期系统需要调用operator new 对应的operator delete,或者调用参数与placement new对应的placement delete。如果找不到这种特别的delete,C++运行期系统就会什么都不做,那个new就无法被释放。
解决办法是:
提供一个正常的operator delete用于构造期间无任何异常抛出的情况;
提供一个placement delete用于期间有异常抛出的情况,额外参数与placement new一致即可。
第二个话题,placement new和delete掩盖了operator new 和delete的正常版本。什么时候会掩盖呢?比如,placement new 和delete定义在class内部,则这个class就看不到全局域里面常规的new和delete了。又比如派生类内定义的new和delete掩盖了他基类和全局域的所有版本。解决办法是让这个class继承自包含所有标准new和delete形式的类(一共三种,如下),然后用using声明露出其他版本。
1 class StandardNewDeleteForms 2 { 3 public: 4 //normal new/delete 5 static void* operator new(std::size_t size) 6 { return ::operator new(size); } 7 static void operator delete(void* pMemory) 8 { ::operator delete(pMemory); } 9 10 //placement new/delete 11 static void* operator new(std::size_t size, void* ptr) 12 { return ::operator new(size,ptr); } 13 static void operator delete(void* pMemory,void* ptr) 14 { ::operator delete(pMemory,ptr);} 15 16 //nothrow new/delete 17 static void* operator new(std::size_t size,const std::nothrow_t& nt) 18 { return ::operator new(size,nt); } 19 static void* operator delete(void* pMemory,const std::nothrow_t&) 20 { ::operator delete(pMemory); } 21 };
1 class Widget:public StandardNewDeleteForms{ 2 public: 3 using StandardNewDeleteForms::operator new; 4 using StandardNewDeleteForms::operator delete;//继承标准形式 5 static void* operator new(std::size_t size,//再加自己的形式 6 std::ostream& logStream); 7 static void operator delete(void* pMemeory, 8 std::ostream& logStream); 9 //... 10 };