天天看点

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[];
}
           

有时候,我们希望对于一些class ,当new 内存失败时能够有它独立的处理函数,那么基于

set_new_handler

的一个使用可以如下:

class Some_class
{
    static new_handler current_handler; //当前类的new_handler
    int c[];
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[L];
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[L];
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 < )
        return malloc(size);
    else return nullptr;
}
//自己编写的new 
void* operator new (size_t size) throw(bad_alloc)
{   
    if(size == )
        size = ;   //处理0 bytes 的处理
    while(true)
    {
        void* p = get_memory(size);
        if(p!=nullptr)
            return p;

        //为了过去全局的handler
        //但是不具备线程安全性
        new_handler global_handler = set_new_handler();
        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 的构造函数
  • 返回指针值

如果在调用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 也被掩盖了。也就是说,如果我们这个使用代码:

也会报错。

为了解决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)

c++

继续阅读