了解new-handler 的行为
operator new 无法满足某一内存分配需求时
- 之前,返回一个null,某些旧编译器这么做(现在也可以指定这么做)
- 现在,调用new-handler
为了指定“用以处理内存不足”的函数,调用set_new_handler 函数:
typedef int (__cdecl *new_hand)(size_t);
new_hand __cdecl _set_new_handler(new_hand);
_STD_BEGIN
static new_handler _New_handler;
int __cdecl _New_handler_interface(size_t) _THROW1(std::bad_alloc)
{ // interface to existing Microsoft _callnewh mechanism
_New_handler();
return (1);
}
_CRTIMP2 new_handler __cdecl set_new_handler(new_handler pnew) _THROW0()
{ // remove current handler
_BEGIN_LOCK(_LOCK_MALLOC) // lock thread to ensure atomicity
new_handler pold = _New_handler;
_New_handler = pnew;
_set_new_handler(pnew ? _New_handler_interface : 0);
return (pold);
_END_LOCK()
}
示例代码如下:
void my_handler()
{
cout << "my_handler error" << endl;
}
int main()
{
new_handler temp = get_new_handler();
temp = set_new_handler(my_handler);
int * pTemp = new int[0x177FFFFF];
//...
}
调用的是,一个静态变量:
就是我们的my_handler 函数。
_callnewh 调用的std::_New_handler_interface 函数
_CRT_SECURITYCRITICAL_ATTRIBUTE
void* __CRTDECL operator new(size_t const size)
{
for (;;)
{
if (void* const block = malloc(size))
{
return block;
}
if (_callnewh(size) == 0)//这里总是返回1
{
if (size == SIZE_MAX)
{
__scrt_throw_std_bad_array_new_length();
}
else
{
__scrt_throw_std_bad_alloc();
}
}
// The new handler was successful; try to allocate again...
// 再次申请
}
}
因此最后的,效果,是,这里的my_handler 反复的被调用
当我们,真正的提供了一个new_handler 函数,需要做下面的事情:
- 让更多的内存可以用
- 安装另一个new_handler ,此时,当,下次调用new_handler 就不再调用自己了。
- 卸载new_handler ,设置为null,此时,如果再次申请失败的话,就将抛出异常
- 抛出band_alloc 异常,这样的异常,不会被operator new 捕获
- 不返回,通常,调用abort 或 exit
实现,自己类相关的new_handler:
class NewHandlerHolder {
public:
explicit NewHandlerHolder(std::new_handler nh):handler(nh){}
~NewHandlerHolder() { std::set_new_handler(handler); }
private:
std::new_handler handler;
NewHandlerHolder(const NewHandlerHolder&);
NewHandlerHolder & operator=(const NewHandlerHolder&);
};
class Widget {
public:
static std::new_handler set_new_handler(std::new_handler p)throw() {
auto old = currentHandler;
currentHandler = p;
return old;
}
static void outOfMemory() { cout << "Widget OutOfMemory" << endl; }
void* operator new(std::size_t size) throw(std::bad_alloc);
private:
static std::new_handler currentHandler;
};
std::new_handler Widget::currentHandler = NULL;
void* Widget::operator new(size_t size)
{
NewHandlerHolder temp(std::set_new_handler(currentHandler));
return ::operator new(size);
}
int main()
{
Widget::set_new_handler(Widget::outOfMemory);// 不一定是静态成员函数,符合要求即可
Widget* pWidget = new Widget();
getchar();
getchar();
return 0;
}
当然,我们可以使用这个类,来生成每个类专属的,异常处理函数:
class NewHandlerHolder {
public:
explicit NewHandlerHolder(std::new_handler nh):handler(nh){}
~NewHandlerHolder() { std::set_new_handler(handler); }
private:
std::new_handler handler;
NewHandlerHolder(const NewHandlerHolder&);
NewHandlerHolder & operator=(const NewHandlerHolder&);
};
template <typename C>
class NewHandlerSupport {
public:
static std::new_handler set_new_handler(std::new_handler p)throw() {
auto old = currentHandler;
currentHandler = p;
return old;
}
void* operator new(std::size_t size) throw(std::bad_alloc);
private:
static std::new_handler currentHandler;
};
template <typename C>
std::new_handler NewHandlerSupport<C>::currentHandler = NULL;
template <typename C>
void* NewHandlerSupport<C>::operator new(size_t size)
{
NewHandlerHolder temp(std::set_new_handler(currentHandler));
return ::operator new(size);
}
class Widget :public NewHandlerSupport<Widget> {
//...
public:
Widget() { cout << "Widget init" << endl; }
private:
int a[0x17FFFFFF];
};
void outOfMemory()
{
cout << "Widget :: outOfMemory " << endl;
}
int main()
{
Widget::set_new_handler(outOfMemory);
Widget* temp= new Widget();
getchar();
getchar();
return 0;
}
这里,NewHandlerSupport 没有使用< T > 做任何动作,我们只是希望,继承自NewHandlerSuport 的每个class,拥有实体不同的NewHandlerSupport 复件(更明确的说,其static currentHandler)。类型参数T 只是用来区分不同的派生类。Template 机制会自动为每一个T 生成一个currentHandler。
如果想在申请失败的时候,单纯的返回null,如果做
- #include< new >
- Widget* pw2 = new(std::nothrow) Widget; //如果分配失败,返回0
但,仅仅针对的是,内存分配失败的时候,不会抛出异常,如果,对象初始化的时候,抛出异常,依然会异常。
了解new 和 delete 的合理替换时机
三个常见理由:
- 检测运用上的错误,比如,在前后,加上一个签名,防止,前后溢出
- 强化效能,比如,如果我们的内存申请有很强的相关性,大小对齐,就可以定制自己的new、delete,或者加上资源池等功能,可提高性能。自带的new、delete 针对大块内存、小块内存、大小混合内存有同样的态度,我们可以提供定制化
- 收集使用上的统计数据
另外:
4. 增加分配和归还的速度,比如,资源池
5. 降低缺省内存管理器带来的空间额外开销
6. 弥补缺省分配器中的非最佳齐位
7. 为了将相关对象成簇集成。减少,内存页错误
8. 获得非传统的行为
编写new 和 delete 时需固守常规
_CRT_SECURITYCRITICAL_ATTRIBUTE
void* __CRTDECL operator new(size_t const size)
{
for (;;)
{
if (void* const block = malloc(size))
{
return block;// 申请成功则返回
}
if (_callnewh(size) == 0)// 如果返回了0 ,表示不能处理了,直接抛出异常
{
if (size == SIZE_MAX)
{
__scrt_throw_std_bad_array_new_length();
}
else
{
__scrt_throw_std_bad_alloc();
}
}
// 返回1 了,表示还有希望,继续执行,再次尝试
// 退出循环的条件:内存被成功分配,或者,new_handler 抛出异常或退出程序
// 或者,__callnewh 返回1,然后,抛出异常,
}
}
这里的_callnewh 是vs2017 中的,新的处理,提供了另一种,机制,上面的,代码里面,我们可以看到,它总是返回1 的,也兼容了之前的处理
另外,父类的,new 和 delete 对于子类来说,可能并不适用, 因此,我们应该讲非父类大小的处理(如果子类重写了这个函数,自然也不会到父类这里来),转发给标准的new 和 delete。
void * Base::operator new(std::size_t size) throw(std::bad_alloc)
{
if (size != sizeof(Base))
return ::operator new(size);
}
void Base::operator delete(void* rawM,std::size_t size)throw()
{
if (rawM == 0) return;
if (size != sizeof(Base)){
::operator delete(rawM);
}
// 现在,归还rawMemory 所指的内存
return;
如果即将被删除的对象派生自某个base class,而后者欠缺virtual 析构函数,c++ 传给operator delete 的size_t 数值可能不正确,这是,让base class 有一个virtual 析构函数的好理由
写了placement new 也要写 placement delete
Widget* pw = new Widget;
这里面共有两个函数被调用,一个,分配内存的operator new,一个Widget 的默认构造函数。
其中,第一个函数调用成功,第二个函数却抛出异常,此时,客户没有能力归还内存,取消步骤一并恢复的责任落到了c++ 运行期系统身上。
问题
** 它 必须知道哪一个(因为可能有许多个)operator delete 应该被调用**
正常的new :
void * operator new(size_t) throw(std::bad_alloc);
// 对应的delete
void operator delete(void* rawMemory) throw();// global 作用域中的正常签名
void operator delete(void* rawMemory,size_t size)throw();// class 作用域中典型的签名式
使用通常的,new 和 delete,运行期系统可以毫无问题的找出那个“知道如何取消new 所作所为并恢复”的delete。
当:
class Widget{
public:
static void* operator new(size_t size,std::ostream& logStream) throw(std::bad_alloc);
它找不到了,你需要提供对应的:
class Widget{
public:
static void operator delete(void* pMemory,std::ostream& logStream) throw();
另外,为了,避免,前几节所说的,名称掩盖的问题,
默认,c++ 在global 作用域内提供以下形式的operator new:
void* operator new(std::size_t) throw(std::bad_alloc);
void* operator new(std::size_t ,void*) throw();
void* operator new(std::size_t,const std::nothrow_t&) throw();
除非要阻止class 的客户使用这些形式,否则,确保他们在你所生成的任何定制型operator new 还可用。对于每一个可用的operator new 也应该确定提供对应的operator delete。如果你希望这些函数有着平常的行为,只要令你的class 专属版本调用global 版本即可。