new 与 delete 操作符

Posted 小键233

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了new 与 delete 操作符相关的知识,希望对你有一定的参考价值。

new 和 delete 是C++ 中一对动态申请内存的操作符。

new_handler 行为

在std的命名空间中,有这样的代码:

namespace std

    typedef void (*) () new_handler;
    new_handler set_new_handler(new_handler p) throw();

set_new_handler的作用是,允许用户设置当前operator new 失败时的函数,就是new_handler
它会返回之前的new_handler
很一般的使用如下:

void out_of_memory()

    cerr<<"out !"<<endl;


int main()

    set_new_handler(out_of_memory);
    int* p = new int[100000000000000L];

有时候,我们希望对于一些class ,当new 内存失败时能够有它独立的处理函数,那么基于set_new_handler 的一个使用可以如下:

class Some_class

    static new_handler current_handler; //当前类的new_handler
    int c[1000000000000L];
public:
    Some_class()
    static new_handler set_new_handler(new_handler inn)
    
        new_handler old(current_handler);
        current_handler = inn;
        return old;
    
    static void* operator new (size_t size)
    
        new_handler old = ::set_new_handler(current_handler);
        void* p = ::operator new(size);
        set_new_handler(old); //需要恢复全局的new_handler
        return p;
    
;

new_handler Some_class::current_handler = nullptr;

在以前的博文中,有提到利用类来管理资源。
这里其实也可以运用一样的手法,来自动设置和恢复new_handler

于是,再更改一下,就变成这样了:

class New_handler_holder

    new_handler old_handler;

    //防止copy
    New_handler_holder(const New_handler_holder&);
    New_handler_holder& operator = (const New_handler_holder&);
public:
    New_handler_holder(new_handler inn)
    
        old_handler = set_new_handler(inn);
    
    ~New_handler_holder()
    
        set_new_handler(old_handler);
    
;

class Some_class

    static new_handler current_handler;
    int c[1000000000000L];
public:
    Some_class()
    static new_handler set_new_handler(new_handler inn)
    
        new_handler old(current_handler);
        current_handler = inn;
        return old;
    
    static void* operator new (size_t size)
    
        New_handler_holder h(current_handler); //这里这样运用
        return ::operator new(size);
    
;

new_handler Some_class::current_handler = nullptr;

如果每次类需要自己的new_handler 的时候,都要编写一段几乎相同的代码。
嗯~于是,我们可以利用模板的技术,帮我们完成代码的复用。
先声明一个类,用来包含new_handler:

template<typename T>
class New_handler_support

    static new_handler current_handler;

public:
    static new_handler set_new_handler(new_handler inn)
    
        new_handler old(inn);
        current_handler = inn;
        return old;
    
    static void* operator new (size_t size)
    
        New_handler_holder h(current_handler);
        return ::operator new(size);
    
;

template<typename T>
new_handler New_handler_support<T>::current_handler = nullptr;

然后使用的时候,让需要有自己new_handler 的类继承它就可以了:

//虽然是public 继承,但不是is-a 关系!!
class Some_class:public New_handler_support<Some_class>

    int c[1000000000000L];
public:
    Some_class() 
    //不需要重新编写operator new 函数
;

编写new 和 delete 时的规则

在global 的作用域中,存在着关于new 和 delete 的函数。
但C++ 提供了操作符重载的机制,所以,我们也可以自己编写 new 和 delete 函数。
至于为什么需要自己编写和什么时候适合自己编写 new 和 delete 函数,在[1] 中的条款50 中有说明。

首先,从operator new 开始。
我们知道,在正常的情况下,如果new 一个内存失败的时候,在抛出一个bad_alloc异常之前,会循环调用new_handler 的函数,直到内存分配成功。
它也要能够处理分配内存为0 时的情况。C++ 规定,即使客户要求0 bytes,operator new 也得返回一个合理的指针[1]。那么在处理这个情况时,可以返回指向1 个byte 的指针。

所以,operator new 应该遵守以下的规则[1]:

  • 应该包含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler.
  • 应该能够处理0 bytes 的申请。

于是,我们可以这么写一段简单的实例代码:

//这是分配内存的行为,为了方便,直接使用malloc
//但是实际开发中,这部分可能与自己项目的内存管理相关
void* get_memory(size_t size)

    if(size < 1000)
        return malloc(size);
    else return nullptr;

//自己编写的new 
void* operator new (size_t size) throw(bad_alloc)
   
    if(size == 0)
        size = 1;   //处理0 bytes 的处理
    while(true)
    
        void* p = get_memory(size);
        if(p!=nullptr)
            return p;

        //为了过去全局的handler
        //但是不具备线程安全性
        new_handler global_handler = set_new_handler(0);
        set_new_handler(global_handler);

        if(global_handler) global_handler();
        else    throw bad_alloc();
    
;

对于delete ,它应该能够处理空指针的delete ,于是:


void delete_memory(void* ptr)

    free(ptr);


//自己编写的delete
void operator delete (void* ptr) throw()

    if(ptr == nullptr)  return; //如果是空指针,那么直接返回
    delete_memory(ptr);

这是一般的new 和delete。
现在我们探讨一下class 的new 和delete。

class Base

public:
    static void* operator new (size_t size)
    
        //...
    
    static void operator delete (void* ptr, size_t size ) throw() 
    
        //...
    
;

现在,如果有class 继承了Base,那么它也会继承Base 的new。

class Derived:public Base

//...

Derived* pd = new Derived; //调用了base版本的new

这时候如果new 了Derived class,它会调用base 版本的new ,这明显不是我们想要的。
所以,class 专属版本的new 还应该能够处理大小和自身不同的内存申请要求。[1]

class Base

public:
    static void* operator new (size_t size)
    
        if(size != sizeof(Base))
            ::operator new(size);  //如果大小不等于自身,交给全局的new
        else 
            return get_memory(size);
    
    static void operator delete (void* ptr, size_t size ) throw() 
    
        if(ptr== nullptr) return;
        if(size != sizeof(Base)) return ::operator delete(ptr);
        delete_memory(ptr);
    
;

当然这里面的情况更加复杂一点,如果Base 和Derived 的sizeof 大小是一样的呢?
如果从不会成为基类的类的new,就可以不用检验与自身大小不一致的new ?

我觉得,最好的解决方案是视自己的需求而定,没有最好的技术方案,只有最合适的技术方案。

placement new 与 placement delete

关于placement new 和placement delete 的若干术语[1]:

placement new: 如果operator new 接受的参数除了一定会有的那么size_t 之外,还有其他,这就是所谓的placement new
placement delete : 如果operator delete 接受额外的参数,便称为placement delete

当new 一个对象的时候:

Some_class* ps = new Some_class;

这一过程如下:

  • 分配内存
  • 调用Some_class 的构造函数
  • 返回指针值

如果在调用Some_class 的构造函数中,抛出异常怎么办?
在这个时候,我们是没有办法显示地去回收内存。那这个职责就落到了运行期系统身上。运行期系统会调用与operate new 相应的operator delete 回收内存。

那如果运行期系统找不到相应的delete 函数呢?它两手一摊,表示我无能为力。那这个时候,就发生臭名昭彰的内存泄露了。

就正常的operator new 函数,原型如下[1]:

void* operator new (size_t size) throw(bad_alloc);

对应的operator delete 如下:

void operator delete(void* ptr) throw(); //global 中正常的签名式
void operator delete(void* ptr, size_t size) throw();//class 作用域中典型的签名式

那如果new 的时候调用了placement new 函数,就placement delete 函数就要存在,否则在应对new 抛出异常的情况下,就无法回收内存了。

假设有个类,它重载operator new ,并额外接受一个参数用来输出一些信息,像这样:

class Some_class

public:
    static void* operator new (size_t s, ostream& out) throw(bad_alloc)
    
        out<<" Some_class is new "<<endl;
        return malloc(s);
    
;

其中的new 函数就是placement new。

那它也要有相对应的placement delete函数:

    static void operator delete (void* ptr, ostream& out) throw();

那现在这个类的定义如下:

class Some_class

public:
    static void* operator new (size_t s, ostream& out) throw(bad_alloc)
    
        out<<" Some_class is new "<<endl;
        return malloc(s);
    
    static void operator delete (void* ptr, ostream& out) throw()
    
        out<<" Some_class is delete "<<endl;
        return free(ptr);
    
;

一切都很好,现在我们来试着使用一下这个类:

SomeClass* ps = new (cout) Some_class;//ok
delete ps;//error

然后,发现,new 是正常的,但是delete 的时候却报错了。
那是因为placement delete 只有在“伴随placement new 调用而出发的构造函数出现异常时”才会被调用。[1]

为了理解,不妨再看一遍加粗的字。

也就是说,把placement delete 写出来并不是为了我们自己调用,而是为了解决“伴随placement new 调用而出发的构造函数出现异常时”回收内存的问题。它只是我们编写过程中的一个安全保证

那么,delete 一个指针的时候,调用的是正常的delete函数。但是上面的类的new 和 delete 声明,掩盖掉了global 作用域下的new 和 delete 函数,所以,我们需要重新声明:

class Some_class

public:
    static void* operator new (size_t s, ostream& out) throw(bad_alloc)
    
        out<<" Some_class is new "<<endl;
        return malloc(s);
    
    static void operator delete (void* ptr, ostream& out) throw()
    
        out<<" Some_class is delete "<<endl;
        return free(ptr);
    
    static void operator delete (void* ptr) throw()
    
        cout<<" ok ,now is normal delete!"<<endl;
        return free(ptr);
    
;

事实上,正常的new 也被掩盖了。也就是说,如果我们这个使用代码:

Some_class * ps = new Some_class;

也会报错。

为了解决class 版本下,需要额外定义使用placement new 和delete ,我们可以声明一个基类,然后让别的类继承它,使用using 使得函数可见,就ok了:

class Stander_new_delete_forms

public:
    // normal new delete
    static void* operator new (size_t size) throw(bad_alloc) 
       return ::operator new(size); 
    static void operator delete (void* ptr) throw()
       return ::operator delete(ptr); 

    //placement new delete
    static void* operator new (size_t size, void* ptr) throw()
       return ::operator new(size,ptr); 
    static void operator delete (void* p_memory, void* ptr) throw()
       return ::operator delete(p_memory, ptr); 

    //nothrow new delete
    static void* operator new(size_t size, const nothrow_t& nt) throw()
       return ::operator new(size, nt); 
    static void operator delete(void* ptr, const nothrow_t& nt) throw()
       return ::operator delete(ptr, nt); 
;


class Some_class: public Stander_new_delete_forms

public:
    using Stander_new_delete_forms::operator new; //使用using
    using Stander_new_delete_forms::operator delete;
    static void* operator new (size_t s, ostream& out) throw(bad_alloc)
    
        out<<" Some_class is new "<<endl;
        return malloc(s);
    
    static void operator delete (void* ptr, ostream& out) throw()
    
        out<<" Some_class is delete "<<endl;
        return free(ptr);
    
;

global 作用域下的new 和 delete

global 作用域下提供了三对的new 和 delete。

void* operator new (size_t size) throw(bad_alloc) 
void* operator new (size_t size, void* ptr) throw()
void* operator new(size_t size, const nothrow_t& nt) throw()


void operator delete (void* ptr) throw()
void operator delete (void* p_memory, void* ptr) throw()
void operator delete(void* ptr,const nothrow_t& nt) throw()

分别对应normal new、placement new 和 nothrow new。
normal new 就不提了,经常使用的就是它。

placement new 的是用于指向一个对象该被构造之处。
一个对象的内存通常是固定的,这个版本的作用就是在一个已经开辟内存的对象上,重新构造对象。
有点像在废旧的城池上重新建立江山的意思。使用如下:

    Some_class* ps = new Some_class;
    ps->~Some_class(); //废旧的城池
    void* pv = ps;
    ps = new (pv) Some_class; //重新利用
    delete ps;

nothrow 的意思就是new 过程中不抛出异常。如果内存分配不成功,那么返回空指针。

    Some_class* pns = new (nothrow) Some_class;
    if(pns != nullptr)
        //...

[参考资料]
[1] Scott Meyers 著, 侯捷译. Effective C++ 中文版: 改善程序技术与设计思维的 55 个有效做法[M]. 电子工业出版社, 2011.
(条款49:了解new_handler 行为;
条款51:编写new 和 delete 时需固守常规;
条款52:写了placement new 也要写placement delete)

以上是关于new 与 delete 操作符的主要内容,如果未能解决你的问题,请参考以下文章

定制new和delete

八定制new和delete

malloc/free与new/delete的区别与联系

深入了解C++中各种不同意义的new和delete

new/delete 与 malloc/free的区别

C++中的newoperator new与placement new