placement new和placement delete
参考:《Effective C++》
(1) 什么是placement new和placement delete
placement new和placement delete已经被纳入了C++标准库中
下面是codeblock库中< new >对于二者的声明。
// Default placement versions of operator new.
inline void* operator new(std::size_t, void* __p);
inline void* operator new[](std::size_t, void* __p);
// Default placement versions of operator delete.
inline void operator delete (void*, void*);
inline void operator delete[](void*, void*);
placement版本的new和delete初衷是用于在vector中未使用的空间上创建对象的。
如果不特别说明,我们口中的placement 版本的new和delete指的就是上面的几种。
当然我们也可以自定义一些placement 版本(比如带有log功能的new):
void * operator new(size_t size,std::ostream & logger)
throw(std::bad_alloc);
(2) 如何使用placement版本的new和delete
#include <iostream>
#include <new>
#define BUFSIZ 512
char *BUF[BUFSIZ]{0};//申请一片内存
int main(){
std::cout<<"BUF Addr:"<<BUF<<std::endl;
int *p(new (BUF)int[10]);//在BUF内分配一块内存给10个int类型的数据
std::cout<<"P Addr:"<<p<<std::endl;
for(int i=0;i<10;i++){p[i]=i;}
for(int i=0;i<10;i++){std::cout<<p[i]<<" ";}
std::cout<<'\n';
return 0;
}
上面的例子就是在模拟vector的内存分配:在已有的但是未使用的BUF中分配内存。
(3) 从内存泄露出发
为什么我们需要placement版本的new和delete呢?
答:实际的需要。就像vector的内存处理方式。
我们不如从内存泄漏出发,细说placement版本的new和delete。
(a) 什么时候new一个对象会发生内存泄露?
#include <iostream>
#include <new>
#include <cstdlib>
class demo{
public:
demo():m_dat(100){
throw 1;
}
//palcement版本的new和delete
void *operator new(size_t size,std::ostream&logger)throw(std::bad_alloc){
return malloc(size);
}
//非placement版本的new和delte
void operator delete(void *p)throw(){
free(p);
}
private:
int m_dat;
};
int main(){
demo *p(new (std::cerr)demo);
return 0;
}
当new(std::cerr)demo被调用时,new操作成功并返回一块内存,但是demo的构造函数却抛出了异常。当然C++运行期系统考虑到了这种情况,当该情况发生时,系统会自动调用相应版本的delete正确地释放这块内存。
但是new的版本非常多,系统怎么知道调用那个版本呢?
答:系统会寻找参数个数和类型都与new相同的某个delete(这里指的是额外的参数相同,额外的参数指除了new需要size_t和delete需要void*之外的参数)
所以,我们容易知道,上述代码的内存泄露在:palcement版本的new没有对应的placement版本的delete可供调用,所以系统不做任何操作。
解决方法很简单,重载一个相应的operator delete即可:
class demo{
public:
demo():m_dat(100){
throw 1;
}
//palcement版本的new和delete
void *operator new(size_t size,std::ostream&logger)throw(std::bad_alloc){
return malloc(size);
}
//非placement版本的new和delte
void operator delete(void *p)throw(){
free(p);
}
//重载相应的placement版本的delete
void operator delete(void *p,std::ostream&logger)throw(){
std::cout<<"Placement operator delete"<<std::endl;
free(p);
}
private:
int m_dat;
};
这样,系统就能够正确地选择new和delete。
所以铭记:重载了new就要重载相应版本的delete
(b) 当placement new和placement delete遇到继承
问题源:任何版本的new和delete重载都会覆盖默认的版本。
默认版本的new和delete有这些:
void *operator new(size_t)throw(std::bad_alloc);
void *operator new(size_t,void*)throw();
void *operator new(size_t,const std::nothrow_t&)throw();
当上述任一版本的new都会被类中的任一版本的new重载所覆盖
上例子:
#include <iostream>
#include <new>
#include <cstdlib>
class demo{
public:
demo():m_dat(100){
throw 1;
}
//重载自定义版本的new
void *operator new(size_t size,std::ostream&logger)throw(std::bad_alloc){
return malloc(size);
}
//重载相应的delete
void operator delete(void *p,std::ostream&logger)throw(){
std::cout<<"Placement operator delete"<<std::endl;
free(p);
}
private:
int m_dat;
};
int main(){
demo *p(new demo);
return 0;
}
codeblock的错误:
VS2015的错误:
我在:重载new和delete的一些规则的特殊规则1中提及:一个类的operator new和operator delete 大都是为了本个类服务的,而不是它的派生类。
所以,当一个类重载了new和delete,它就会覆盖全局的,或是基类的new和delete。有时我们需要将这些版本的(比如基类中的)new和delete在本类中同样可以使用,我们该如何做呢?
*如果没有涉及到继承,而是局部和全局之间的覆盖的话,可以直接使用双冒号(::)来调用全局的new和delete,例如:::operator new(size)
让类支持global版本的new和delete(通过继承和using)
#include <iostream>
#include <new>
#include <cstdlib>
class StandardNewDelete{
public:
static void *operator new(size_t size)throw(std::bad_alloc){return ::operator new(size);}
static void *operator new(size_t size,void *ptr)throw(){return ::operator new(size,ptr);}
static void *operator new(size_t size,const std::nothrow_t&nt)throw(){return ::operator new(size,nt);}
static void operator delete(void *p)throw(){::operator delete(p);}
static void operator delete(void *p,void *ptr)throw(){::operator delete(p,ptr);}
static void operator delete(void *p,const std::nothrow_t& nt)throw(){::operator delete(p,nt);}
};
class demo:public StandardNewDelete{
public:
demo():m_dat(100){}
//使该类支持global版本的new和delete
using StandardNewDelete::operator new;
using StandardNewDelete::operator delete;
void *operator new(size_t size,std::ostream&logger)throw(std::bad_alloc){
return malloc(size);
}
void operator delete(void *p,std::ostream&logger)throw(){
std::cout<<"Placement operator delete"<<std::endl;
free(p);
}
private:
int m_dat;
};
int main(){
demo *p(new demo);
return 0;
}