定制new 和 delete

了解new-handler 的行为

operator new 无法满足某一内存分配需求时

  1. 之前,返回一个null,某些旧编译器这么做(现在也可以指定这么做)
  2. 现在,调用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 函数,需要做下面的事情:

  1. 让更多的内存可以用
  2. 安装另一个new_handler ,此时,当,下次调用new_handler 就不再调用自己了。
  3. 卸载new_handler ,设置为null,此时,如果再次申请失败的话,就将抛出异常
  4. 抛出band_alloc 异常,这样的异常,不会被operator new 捕获
  5. 不返回,通常,调用abort 或 exit

实现,自己类相关的new_handler:

扫描二维码关注公众号,回复: 5001621 查看本文章
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,如果做

  1. #include< new >
  2. Widget* pw2 = new(std::nothrow) Widget; //如果分配失败,返回0

但,仅仅针对的是,内存分配失败的时候,不会抛出异常,如果,对象初始化的时候,抛出异常,依然会异常。

了解new 和 delete 的合理替换时机

三个常见理由:

  1. 检测运用上的错误,比如,在前后,加上一个签名,防止,前后溢出
  2. 强化效能,比如,如果我们的内存申请有很强的相关性,大小对齐,就可以定制自己的new、delete,或者加上资源池等功能,可提高性能。自带的new、delete 针对大块内存、小块内存、大小混合内存有同样的态度,我们可以提供定制化
  3. 收集使用上的统计数据

另外:
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 版本即可。

猜你喜欢

转载自blog.csdn.net/qq_18218335/article/details/85015973