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)

时间: 2024-08-05 22:26:35

new 与 delete 操作符的相关文章

C++ new delete操作符

//new delete操作符 #define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; /* 1.new delete 操作符号 都是 c++的关键字 类似于 C语言中的 malloc free 2.定义对象时,使用了new关键字,会为这个对象在堆上分配内存,不使用new 关键字会直接在栈上分配内存 new 关键字可以分配基础类型内存,数组类型内存,对象类型内存 对于基础类型内存和数组类型内存,new de

js中的内部属性与delete操作符

本文正式地址:http://www.xiabingbao.com/javascript/2015/08/03/javascript-delete-configurable 在讲解Configurable之前,我们首先来看一道面试题: a = 1; console.log( window.a ); // 1 console.log( delete window.a ); // false console.log( window.a ); // 1 var b = 2; console.log( w

js小知识 delete操作符

说明:delete操作符用于删除对象的某个属性. 语法: delete object.property //删除 对象.属性 delete object['property'] //删除 对象['属性'] 返回值:bool类型,成功删除的时候回返回true,否则返回false. 注意: 如果你删除的属性在对象上不存在,那么delete将不会起作用,但仍会返回true 如果 delete 操作符删除成功,则被删除的属性将从所属的对象上彻底消失.然后,如果该对象的原型链上有一个同名属性,则该对象会从

Javascript的变量与delete操作符

原文:http://charlee.li/javascript-variables-and-delete-operator.html 刚刚看到一篇好文(原文链接), 对Javascript中的delete操作符分析得很透彻.在这里简单地介绍一下内容. 虽然是一个小小的delete操作符,其行为却异常复杂. # Javascript的变量 实际上Javascript中,变量 = 对象属性,这是因为 Javascript 在执行脚本之前 会创建一个Global对象,所有的全局变量都是这个Global

C++之new和delete操作符

在C语言中的动态分配和释放内存的函数是malloc calloc 和 free , 而在C++中要用 new new[] delete delete[] 来申请动态空间和释放空间. 注意:的是new.new[].delete和delete[]是操作符,而非函数:new和delete也是C++的关键字. 操作符new用于动态分配单个空间,而new[]则是用于动态分配一个数组,操作符delete用于释放由new分配的空间,delete[]则用于释放new[]分配的一个数组. 下面是简单的实例代码 u

[转]深入详解javascript之delete操作符

最近重新温习JS,对delete操作符一直处于一知半解的状态,偶然发现一篇文章,对此作了非常细致深入的解释,看完有茅塞顿开的感觉,不敢独享,大致翻译如下. 原文地址:http://perfectionkills.com/understanding-delete/ P.S. 作者是PrototypeJS的开发组成员之一 ========分割线======== 在开始之前,先让我们看一段代码 Js代码     >>> var sum = function(a, b) {return a + 

c++ new和delete操作符的重载

先分析一条语句: Base *pb= new Base; 这条语句是怎么执行的?首先,这个函数调用标准C++的operator new()函数,然后调用Base的构造函数. 如果我们自定义new时,可以自定义一个new,如 class Base { static void* operator new(size_t size) { } static void operator delete(void *p) { } } 这个Base类里面的new 和delete覆盖掉标准new和标准delete,

javascript delete操作符

delete 操作符是从某个对象上移除指定属性,成功删除的时候返回 true ,否则返回 false. 如果删除的属性在对象上不存在,那么 delete 将不起作用,但仍会返回 true. var person = { age:100, name:"yangguo", } console.log(person.name);//yangguo console.log(delete person.name);//true console.log(person.name);//undefin

JS的 delete操作符 删除对象属性

JS如何删除对象中的某一属性 var obj={ name: 'zhagnsan', age: 19 } delete obj.name //true typeof obj.name //undefined 通过delete操作符, 可以实现对对象属性的删除操作, 返回值是布尔 如果你试图删除的属性不存在,那么delete将不会起任何作用,但仍会返回true 如果对象的原型链上有一个与待删除属性同名的属性,那么删除属性之后,对象会使用原型链上的那个属性(也就是说,delete操作只会在自身的属性

2.16 C++类与new和delete操作符

参考: http://www.weixueyuan.net/view/6347.html 总结: 当我们需要为类对象动态分配存储空间时,我们应该使用C++语言提供的new与new[]操作符,而不要使用C语言提供的malloc函数. 虽然malloc函数具有分配存储空间的功能,但是这些函数除了分配存储空间外,不会调用类的构造函数.而C++语言提供的new和new[]操作符则不会如此,使用它们为对象分配存储空间的同时,它们也会调用相应的构造函数. 操作符delete和delete[]在释放对象存储空