当我们使用了new关键字去创建一个对象时,你知道背后做了哪些事情吗?AanewA; 实际上这样简单的一行语句,背后做了以下三件事情:分配内存,如果类A重载了operatornew,那么将调用A::operatornew(sizet)来完成,如果没有重载,就调用::operatornew(sizet),即全局new操作符来完成。调用构造函数生成类对象;返回相应指针。 下面我们通过一个例子来验证这个过程:includeiostreamincludestringincludemalloc。studentclassclassStu{public:Stu(stringname,intage){coutcallS};public:voidprint()const{coutnamenamestd::coutageagestd::};voidoperatornew(sizetsize){std::coutcalloperatornewstd::returnmalloc(size);}private:};intmain(){Stustu1newStu(a,10);} 在上述代码中,我们重载了Stu类的operatornew操作符,用来验证上述对于new关键字的描述。 上述代码的执行结果如下所示:calloperatornewcallStuclassconstructor 可以看到重载的operatornew被调用,类Stu的构造函数也被调用,验证了上述的描述。 要注意到的是new是一个关键字,和sizeof一样,我们不能修改其具体功能。operatornew 从new的调用过程中,我们知道会调用operatornew操作符 那么operatornew又是什么呢? C支持运算符的重载,支持对一些运算符自定义其行为: operatornew operatornew是一个操作符,和操作符一样,作用是分配空间。我们可以重写它们,修改分配空间的方式。 operatornew返回值必须是void。第一个参数必须是sizetvoidoperatornew(std::sizetsize)throw(std::badalloc);voidoperatornew(std::sizetsize,conststd::nothrowtnothrowconstant)throw(); 在下面的例子中,我们使用重载了三个operatornew方法,并分别调用。includeiostreamincludestringincludemalloc。studentclassclassStu{public:Stu(stringname,intage){coutcallS};Stu(){}public:voidprint()const{coutnamenamestd::coutageagestd::};voidoperatornew(sizetsize){std::coutcalloperatornewstd::returnmalloc(size);}voidoperatornew(sizetsize,intnum){std::coutcalloperatornewwithintstd::returnmalloc(size);}voidoperatornew(sizetsize,charc){std::coutcalloperatornewwithcharstd::returnmalloc(size);}private:};intmain(){Stustu1newStu(a,10);Stustu2new(1)Stu(a,10);Stustu3new(c)Stu(a,10);deletestu1;deletestu2;deletestu3;} 执行结果如下:calloperatornewcallStuclassconstructorcalloperatornewwithintcallStuclassconstructorcalloperatornewwithcharcallStuclassconstructorcalldestructorcalldestructorcalldestructor 可以看到重载的三个operatornew被成功调用。placementnew placementnew是operatornew的一种重载形式,其作用是可以在指定的内存地址创建对象。 placementnew返回值必须是void。第一个参数必须是sizet,第二个参数是voidvoidoperatornew(std::sizetsize,voidptr)throw(); 下面的是一个关于placementnew的调用例子:includeiostreamincludestringincludemalloc。studentclassclassStu{public:Stu(stringname,intage){};Stu(){}public:voidprint()const{coutnamenamestd::coutageagestd::};voidoperatornew(sizetsize,voidp){std::coutplacementnewstd::};private:};intmain(){charbuff(char)malloc(sizeof(Stu));Stustunew(buff)Stu(stu1,10);stuprint();stuStu();free(buff);} 执行结果如下:placementnewnamestu1age10calldestructor 由于placementnew可以在一个指定的位置创建对象,因此在STL中有很广泛的运用,例子vector容器初始化的时候,会使用allocator申请一定的内存,当使用pushback放入对象时,就可以使用placementnew在申请的位置创建对象。 这里以MyTinySTL中创建对象的函数为例,construct。hopeninnewwindow,可以看出construct函数就是使用了全局的placementnew方法在指定地址创建对象。templateclassTy,class。。。Argsvoidconstruct(Typtr,Args。。。args){::new((void)ptr)Ty(mystl::forward(args)。。。);} 需要注意的是,placementnew只是在指定的内存地址上构建对象,在对象使用完毕之后,需要手动调用析构函数做资源的析构。charbuff(char)malloc(4096);Stustunew(buff)Stu(stu1,10);stuprint();stuStu();free(buff); 为什么需要这样呢? 我们知道,使用delete关键字去释放一个对象时,首先会调用析构函数,然后再调用operatordelete释放内存。 但是由于内存的申请并不是通过new对象而申请的,而是通过malloc申请到的一块内存空间,placementnew只有借用了该内存空间去构建了对象,因此是不能使用delete关键字去直接释放该对象的。由于不能直接调用deletestu,这就会导致对象的析构函数不会被自动调用,因此我们需要手动调用对象的析构函数做对象的析构stuStu(),最后使用与malloc配套的free释放申请到的内存空间。 此外,我们讨论另外一个问题,placementnew可以在栈上构建对象吗? 答案是肯定的。includeiostreamincludestringincludemalloc。studentclassclassStu{public:Stu(stringname,intage){};Stu(){}public:voidprint()const{coutnamenamestd::coutageagestd::};voidoperatornew(sizetsize,voidp){std::coutplacementnewstd::};private:};intmain(){charbuff〔4096〕;Stustunew(buff)Stu(stu1,10);stuprint();stuStu();} 与在堆上调用placementnew一样,同样需要手动调用析构函数做资源的释放。但是由于内存是在栈上的,因此不需要手动释放。结论 对于new,operatornew和placementnew三者的区别,我们总结如下: new: new是一个关键字,不能被重载。 new操作符的执行过程如下:调用operatornew分配内存;调用构造函数生成类对象;返回相应指针。 operatornew: operatornew就像operator一样,是可以重载的。如果类中没有重载operatornew,那么调用的就是全局的::operatornew来完成堆的分配。同理,operatornew〔〕、operatordelete、operatordelete〔〕也是可以重载的。 placementnew: placementnew和operatornew并没有本质区别。它们都是operatornew操作符的重载,只是参数不相同。 placement并不分配内存,只是返回指向已经分配好的某段内存的一个指针。因此不能使用delete关键字删除它,需要手动调用对象的析构函数。 如果你想在已经分配的内存中创建一个对象,使用new时行不通的。也就是说placementnew允许你在一个已经分配好的内存中(栈或者堆中)构造一个新的对象。原型中voidp实际上就是指向一个已经分配好的内存缓冲区的的首地址。