注意: new operator和operator new 以及placement new的差异【转载】

1:前言:

本文是在参考了几个博客之后的个人总结,最后贴上自己在理解时使用的测试代码,以及自己对原博文的理解,原博文的地址请留意文章后面的参考资料。对于本文的总结理解有疑问或错误的地方,欢迎留言讨论或指正,大家一起进步,谢谢;

首先我们区分下几个容易混淆的关键词:

new也称为【new operator】 、 operator new 、 placement new

new和delete操作符我们应该都用过,它们是对堆中的内存进行申请和释放,而这两个都是不能被重载的。要实现不同的内存分配行为,需要重载operator new,而不是newdelete。同样placement new也不可以重载;

看如下代码:

class MyClass {…};

MyClass * p = new MyClass;

这里的new就是常规的new不是operator new, 实际上是执行如下3个过程:

1. 调用operator new分配内存;
2. 调用构造函数生成类对象;
3. 返回相应指针。
// 上述new的过程类似所做的工作如下:
// 调用new:
A* pa = new A(3);
// 那么上述动态创建一个对象的过程大致相当于以下三句话(只是大致上):
A* pa = (A*)malloc(sizeof(A));
pa->A::A(3); // 注意显示调用构造函数时的类名::不能省略,否则会报错
return pa;

operator new就像operator+一样,是可以重载的,但是不能在全局对原型为void operator new(size_t size)这个原型进行重载,一般只能在类中进行重载。如果类中没有重载operator new,那么调用的就是全局的::operator new来完成堆的分配。同理,operator new[]、operator delete、operator delete[]也是可以重载的,一般你重载了其中一个,那么最好把其余三个都重载一遍。

placement newoperator new的一个重载版本,【因此也可以把placement new看做是operator new的一个特殊版本】只是我们很少用到它。如果你想在已经分配的内存中创建一个对象,使用new是不行的。也就是说placement new允许你在一个已经分配好的内存中(栈或堆中)构造一个新的对象。原型中void*p实际上就是指向一个已经分配好的内存缓冲区的的首地址。

我们知道使用new操作符分配内存需要在堆中查找足够大的剩余空间,这个操作速度是很慢的,而且有可能出现无法分配内存的异常(空间不够)。placement new就可以解决这个问题。我们构造对象都是在一个预先准备好了的内存缓冲区中进行,不需要查找内存,内存分配的时间是常数;而且不会出现在程序运行中途出现内存不足的异常。所以,placement new非常适合那些对时间要求比较高,长时间运行不希望被打断的应用程序。

注意:

1) 在C++标准中,对于placement operator new []有如下的说明: placement operator new[] needs implementation-defined amount of additional storage to save a size of array. 所以我们必须申请比原始对象大小多出sizeof(int);个字节来存放对象的个数,或者说数组的大小。

2) 使用方法第二步中的new才是placement new,其实是没有申请内存的,只是调用了构造函数,返回一个指向已经分配好的内存的一个指针,所以对象销毁的时候不需要调用delete释放空间,但必须调用析构函数销毁对象。

3)注:使用placement new构造起来的对象或数组,要显式调用它们的析构函数来销毁(析构函数并不释放对象的内存),千万不要使用delete.这是因为placement new构造起来的对象或数组大小并不一定等于原来分配的内存大小,使用delete会造成内存泄漏或者之后释放内存时出现运行时错误。

2:placement new的使用步骤:

使用步骤
在很多情况下,placement new的使用方法和其他普通的new有所不同。这里提供了它的使用步骤。

第一步  缓存提前分配

有三种方式:
1.为了保证通过placement new使用的缓存区的memory alignmen(内存队列)正确准备,使用普通的new来分配它:在堆上进行分配

class Task ;
char * buff = new [sizeof(Task)]; //分配内存

(请注意auto或者static内存并非都正确地为每一个对象类型排列,所以,你将不能以placement new使用它们。)

2.在栈上进行分配

class Task ;
char buf[N*sizeof(Task)]; //分配内存

3.还有一种方式,就是直接通过地址来使用。(必须是有意义的地址)

void* buf = reinterpret_cast<void*> (0xF00F);
第二步:对象的分配

在刚才已分配的缓存区调用placement new来构造一个对象。

Task *ptask = new (buf) Task;
第三步:使用

按照普通方式使用分配的对象:

ptask->memberfunction();

ptask-> member;

//...
第四步:对象的析构

一旦你使用完这个对象,你必须显式调用它的析构函数来毁灭它。按照下面的方式调用析构函数:

ptask->~Task(); //调用析构函数
第五步:释放

你可以反复利用缓存并给它分配一个新的对象(重复步骤2,3,4)如果你不打算再次使用这个缓存,你可以象这样释放它:

delete [] buf;

跳过任何步骤就可能导致运行时间的崩溃,内存泄露,以及其它的意想不到的情况。如果你确实需要使用placement new,请认真遵循以上的步骤。

3:placement new 的用处和好处:

1:placement new/delete 主要用途是:反复使用一块较大的动态分配成功的内存来构造不同类型的对象或者它们的数组。例如可以先申请一个足够大的字符数组,然后当需要时在它上面构造不同类型的对象或数组。placement new不用担心内存分配失败,因为它根本不分配内存,它只是调用对象的构造函数。

2:placement new的好处:
1)在已分配好的内存上进行对象的构建,构建速度快。
2)已分配好的内存可以反复利用,有效的避免内存碎片问题。

4:new、operator new、placement new的使用方法:

new有三种使用方式:plain new【即普通的new】,nothrow new【即operator new的使用方法】和placement new

(1)plain new顾名思义就是普通的new,就是我们惯常使用的new。在C++中是这样定义的:

    void* operator new(std::size_t) throw(std::bad_alloc);
    void operator delete(void *) throw();

提示:plain new在分配失败的情况下,抛出异常std::bad_alloc而不是返回NULL,因此通过判断返回值是否为NULL是徒劳的。

(2)nothrow new是不抛出异常的运算符new的形式。nothrow new在失败时,返回NULL。定义如下:

void * operator new(std::size_t,const std::nothrow_t&) throw();
void operator delete(void*) throw();

(3)placement new意即“放置”,这种new允许在一块已经分配成功的内存上重新构造对象或对象数组。placement new不用担心内存分配失败,因为它根本不分配内存,它做的唯一一件事情就是调用对象的构造函数。定义如下:

    void* operator new(size_t,void*) ;
    void operator delete(void*,void*);

提示1:palcement new的主要用途就是反复使用一块较大的动态分配的内存来构造不同类型的对象或者他们的数组。

提示2:placement new构造起来的对象或其数组,要显示的调用他们的析构函数来销毁,千万不要使用delete

char* p = new(nothrow) char[100];
long *q1 = new(p) long(100);
int *q2 = new(p) int[100/sizeof(int)];

二.实例
1.plain new/delete.普通的new
定义如下:

void *operator new(std::size_t) throw(std::bad_alloc);
void operator delete(void*) throw();

注:标准C++ plain new失败后抛出标准异常std::bad_alloc而非返回NULL,因此检查返回值是否为NULL判断分配是否成功是徒劳的。
测试程序:

#include "stdafx.h"
#include <iostream>
using namespace std;
char *GetMemory(unsigned long size)
{
    char *p=new char[size];//分配失败,不是返回NULL
    return p;
}
int main()
{
    try
    {
        char *p=GetMemory(10e11);// 分配失败抛出异常std::bad_alloc
      //...........
      if(!p)//徒劳
           cout<<"failure"<<endl;
      delete [] p;
    }   
    catch(const std::bad_alloc &ex)
    {
          cout<<ex.what()<<endl;
    }

    return 0;
}

2.nothrow new/delete不抛出异常的operator new的形式,new失败时返回NULL
定义如下:
复制代码代码如下:

void *operator new(std::size_t,const std::nothrow_t&) throw();
void operator delete(void*) throw();
struct nothrow_t{};  const nothrow_t nothrow;//nothrow作为new的标志性哑元

测试程序:

#include "stdafx.h"
#include <iostream>
#include <new>
using namespace std;
char *GetMemory(unsigned long size)
{
    char *p=new(nothrow) char[size];//分配失败,是返回NULL
    if(NULL==p)
          cout<<"alloc failure!"<<endl;
    return p;
}

int main()
{
    try
    {
          char *p=GetMemory(10e11);
          //...........
          if(p==NULL)
           cout<<"failure"<<endl;
          delete [] p;
    }
    catch(const std::bad_alloc &ex)
    {
          cout<<ex.what()<<endl;
    }

    return 0;
}

3.placement new/delete 主要用途是:反复使用一块较大的动态分配成功的内存来构造不同类型的对象或者它们的数组。例如可以先申请一个足够大的字符数组,然后当需要时在它上面构造不同类型的对象或数组。placement new不用担心内存分配失败,因为它根本不分配内存,它只是调用对象的构造函数。
测试程序:

#include "stdafx.h"
#include <iostream>
#include <new> // 使用placement new注意包含头文件new
using namespace std;
class ADT
{
    int i;
    int j;
public:
    ADT()
    {
    }
    ~ADT()
    {
    }
};

int main()
{
    char *p=new(nothrow) char[sizeof(ADT)+2];
    if(p==NULL)
          cout<<"failure"<<endl;
    ADT *q=new(p) ADT;  //placement new:不必担心失败
    // delete q;//错误!不能在此处调用delete q;

    q->ADT::~ADT();//显示调用析构函数
    delete []p;
    return 0;
}

注:使用placement new构造起来的对象或数组,要显式调用它们的析构函数来销毁(析构函数并不释放对象的内存),千万不要使用delete.这是因为placement new构造起来的对象或数组大小并不一定等于原来分配的内存大小,使用delete会造成内存泄漏或者之后释放内存时出现运行时错误。

小结:

1)三种形式的new都会调用构造函数,比如后面的测试代码:
2)对于直接使用new的判断返回值是否为NULL,经实验确实是徒劳的,直接new而不是new(nothrow)的形式,只会返回异常而不会返回NULL;比如后面的测试代码说明;

// 测试代码
#include<iostream>
using namespace std;

class Test
{
public:
    Test()
    {
        cout << "Test()" << endl;
    }
    ~Test()
    {
        cout << "~Test()" << endl;
    }
};

int main()
{
    // 故意new一个大空间使其申请空间不成功,留意new与new(nothrow)此时的反应
    int *p = new(nothrow) int[3*1024*1024*1024];
    if(p==NULL)
    {
        cout << "new failed" << endl;
    }
    cout << p << endl;

    Test *pt = new Test();

    cout << "=======================\n";
    Test *ptmp = new(nothrow) Test();

    delete pt;
    delete ptmp;

    return 0;
}

运行效果:
new(nothrow)

当以上代码修改如下:

#include<iostream>
using namespace std;

class Test
{
public:
    Test()
    {
        cout << "Test()" << endl;
    }
    ~Test()
    {
        cout << "~Test()" << endl;
    }
};

int main()
{
    // 这里修改不使用new(nothrow)
    int *p = new int[3*1024*1024*1024];
    if(p==NULL)
    {
        cout << "new failed" << endl;
    }
    cout << p << endl;

    Test *pt = new Test();

    cout << "=======================\n";
    Test *ptmp = new(nothrow) Test();

    delete pt;
    delete ptmp;

    return 0;
}

则程序运行效果如下:抛异常,但因为异常没处理导致程序运行crash:
new

这也验证了对于直接new而不是new(nothrow)的形式的话判断返回的指针值是否为NULL;是没有意义的,对返回值为NULL;的判断只对new(nothrow)的形式有意义;

参考资料:
感谢以下博主的辛苦总结与无私分享,谢谢

http://www.jb51.net/article/41524.htm
http://blog.csdn.net/songthin/article/details/1703966
http://blog.csdn.net/zhangxinrun/article/details/5940019
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值