关于newexpression、new operator、operator delete的总结

 C++中有很多绕来绕去的术语,如:指针数组、数组指针、常量指针、指针常量、函数指针、指针函数...,让人看得头晕眼花,就连new这个关键字的使用也有new operator与operator new之分(侯捷翻译的《More Effective C++》条款8中就有描述),但或许C++标准中并没有这么多拗口的术语,只是人们在使用和传播过程中“自造”了它们。其实,我觉得new-expression的使用要比new operator更贴切,也更容易与operator new区分开来,因此下文中将使用new-expression而不使用new operator一词。另外,这里有篇帖子中有关于new operator是否属于C++标准术语的争议:What is difference between "new operator" and "operator new"? 。

 1、new表达式(new-expression)

简单地说,new表达式会处理两件事:一是分配内存空间(调用operator new分配内存),二是在已分配的内存空间上构造对象。new-expression使用举例:

CObject *pobj1 = new CObject;               // new-expression,调用普通版的operator new,然后再调用CObject类的构造函数
CObject *pobj2 = new(std::nothrow) CObject; // new-expression,调用nothrow版的operator new,然后再调用CObject类的构造函数
CObject *pobj3 = (CObject *)operator new(sizeof(CObject));    // 调用普通版的operator new,在本文中不将operator new的单独使用看做new-expression
new (pobj3) CObject;                        // new-expression,调用nothrow版的默认placement new,然后再调用CObject类的构造函数

2、operator new

它其实就像C语言中的malloc一样,只负责分配内存空间,只不过它之中多了对内存分配失败的处理。

3、C++标准库中默认的三种operator new

void* operator new(std::size_t) throw (std::bad_alloc);                   // normal single new
void* operator new(std::size_t, const std::nothrow_t&) throw();           // nothrow single new
inline void* operator new(std::size_t, void* __p) throw() { return __p; } // default placement versions of operator new

【说明】:A)上述三种operator new还有其对应operator new[]版本,这里为了简化描述,只讨论非数组(single new)版本。
          B) 常见的C++标准库如g++使用的libstdc++,clang++使用的libcxx,以及STLport-5.2.1中都有这三种形式的operator new。

(1) 普通版的operator new

其中内含一个无穷循环,并在其中分配内存,如果内存申请失败,且设置了new-handler的话,则调用new-handler进行处理,若未设置new-handler,则抛出std::bad_alloc异常。下面列出了g++-4.4.4版本的实现(在new_op.cc文件中),libcxx以及STLport-5.2.1的实现也都类似。(为了阅读上的方便,省略了部分无关代码):

void *operator new (std::size_t sz) throw (std::bad_alloc)
{
    void *p;
    /* malloc (0) is unpredictable; avoid it.  */
    if (sz == 0)                // 如果申请大小为0,则为其提供1字节大小内存
        sz = 1;
    p = (void *) malloc (sz);
    while (p == 0)
    {
        new_handler handler = __new_handler; // __new_handler 为一个未初始化的的全局变量(编译器默认将其初始化为0),它是一个函数指针,用户可以通过set_new_handler()设置其值
        if (! handler)
            throw bad_alloc();
        handler ();
        p = (void *) malloc (sz);
    }

    return p;
}

【说明】:一个设计良好的new-handler应该做以下一些事之一:
          A) 释放一部分内存供程序使用;
          B) 若它不能释放内存,但知道另外别的new-handler有此能力,则它可以安装此能力的那个new-handler来替代自己;
          C) 卸载new-handler,也就是将new-handler设置为null,这样operator new中分配内存失败时就能抛出异常;
          D) 抛出std::bad_alloc异常;
          E) 终止程序,通常调用abort()或exit()。

(2) nothrow版的operator new

nothrow版的operator new主要是为了老代码,以前C++要求在分配内存失败时,返回null指针。下面列出了g++-4.4.4版本的实现(在new_opnt.cc文件中):

void *operator new (std::size_t sz, const std::nothrow_t&) throw()
{
    void *p;
    /* malloc (0) is unpredictable; avoid it.  */
    if (sz == 0)
        sz = 1;
    p = (void *) malloc (sz);
    while (p == 0)
    {
        new_handler handler = __new_handler;
        if (! handler)
            return 0;
        try
        {
            handler ();
        }
        catch (bad_alloc &)
        {
            return 0;
        }

        p = (void *) malloc (sz);
    }

    return p;
}

【说明】:nothrow版的operator new不抛出异常,并不代表使用nothrow版operator new的new-expression不抛出异常,因为new-expression除了调用operator new之外,还会调用构造函数构造对象(不能保证构造函数不抛出异常哦)。

(3) 默认placement new

当人们谈到placement new时,大多数时候他们谈的都是此默认版本的placement new,其实,它只是众多placement new版本中最有用的一个。placement new的定义为:当operator new接受的参数除了一定有的那个size_t之外还有其他参数,这便是个所谓的placement new。
默认版本的placement new只是简单地将传入的指针返回,我的理解是:传入指针所指向的地址已经是已分配内存的地址,因此此版本的operator new(placement new)不用再分配空间,只是简单地返回传入指针(假装我已经为你分配了空间)。它存在的意义只是为了让new-expression调用(注意它是内联函数,因此并不会造成函数调用额外的开销),满足new-expression的两步操作:内存分配和对象构造。

【说明】:当你为某个类定义了一个placement new时,要记得定义一个相对应的placement delete。因为当你的operator new(placement new)分配内存失败时,运行期系统有责任取消operator new的分配并恢复原样,然而,运行期系统并不知道你的operator new内部是如何操作的,因此它并不清楚怎样取消分配并恢复原样,但是它会尝试寻找一个与operator new参数对应的operator delete(placement delete),因此如果你定义了一个与operator new对应的operator delete,用来取消operator new的分配并恢复原样,这样就能保证不会内存泄漏。

4、其它说明:
(1) 当你重载了一个operator new时,也应该重载一个相对应的operator delete。
(2) 以上讨论仅限于重载全局的operator new,若要为某些类实现专属的operator new,还有不少的注意事项,具体可参考《Effetive C++》。

参考:《Effetive C++》条款49-52
        《More Effective C++》条款8

原文地址:https://www.cnblogs.com/opangle/p/2598803.html