new/delete是c++中动态构造对象的表达式 ,一般情况下的new/delete都是指的new/delete表达式,这是一个操作符,和sizeof一样,不能改变其意义。
new/delete表达式的声明如下:
::(optional) new (placement_params)(optional) ( type )initializer(optional) ::(optional) delete expression
除了全局作用符::和初始化参数,还有个 placement_params,这是不常见的,要理解这个参数的作用,就要了解operator new和placement new。
众所周知,new表达式做了两个工作:1.分配内存;2.在分配的内存上调用构造函数构造对象。比如我们分配一个string对象
string *str = new string(“Kian”);
编译器首先调用operator new分配一块内存,类似于malloc,然后在mem上面调用构造函数,
1.void *mem = operator new(sizeof(string));
2. create string at men.
第二步我们是控制不了的,但是operator new却是可以修改的。
Operator new/delete的声明如下:
void* operator new ( std::size_t count ); void* operator new ( std::size_t count, const std::nothrow_t& tag); void operator delete ( void* ptr ); void operator delete ( void* ptr, const std::nothrow_t& tag);
第二种带参数tag的声明称为nothrow形式,因为现在的operator new如果分配内存失败的话会抛出bad_alloc异常. 有时候我们不想抛出异常,而是根据返回值判断内存分配失败与否,nothrow形式就是这个作用,失败时不抛出异常,而是返回null指针。
我们可以直接重载operator new, 定制自己的内存分配策略,常见的作用是优化内存使用性能。重载operator new不需要看见声明就可以直接使用。我们重定义一个简单的版本:
void* operator new(std::size_t size){ printf("operator new called, size=%d ", size); return malloc(size); } void operator delete(void *ptr){ printf("operator delete called "); free(ptr); } int main(){ int *i = new int(2); delete i; std::string *str = new std::string("Kian"); delete str; }
运行结果:
operator new called, size=4
operator delete called
operator new called, size=4
operator new called, size=17
operator delete called
operator delete called
可是有时候我们希望拥有更多的功能,比如记录内存分配释放的位置,用于检测内存错误,或者直接在已有的内存上构造对象,那么必须定义更多参数,这就需要placement new/delete,声明如下.
void* operator new ( std::size_t count, void* ptr ); void* operator new ( std::size_t count, user-defined-args... ); void operator delete ( void* ptr, void* place); void operator delete ( void* ptr, user-defined-args...);
可以直接在已有内存上构造对象:
void *mem = (void*)malloc(sizeof(int)); int *j = new(mem) int(3); printf("mem=%p, j=%p, *j = %d ",mem, j, *j); free(mem);
运行结果:
mem=0x8a48008, j=0x8a48008, *j = 3
可以看出new直接在mem上面构造了对象。
目前,void* operator new ( std::size_t count, void* ptr )在全局域还不能被重载,但是void* operator new ( std::size_t count, user-defined-args... )可以自由定义。
比如记录内存分配发生的位置:
void* operator new(std::size_t size){ printf("operator new called, size=%d ", size); return malloc(size); } void operator delete(void *ptr){ printf("operator delete called "); free(ptr); } void* operator new(std::size_t size, char* filename, int line){ printf("new called at %s:%d size=%d ", filename, line, size); return ::operator new(size); } void operator delete(void *place, char* filename, int line) { printf("delete called at %s:%d place=%p ", filename, line, place); ::operator delete(place); } int main(){ int *k = new(__FILE__, __LINE__) int(1); printf("k=%p *k=%d ", k, *k); delete k; }
运行结果:
new called at testnew.cpp:41 size=4
operator new called, size=4
k=0x847f008 *k=1
operator delete called
在每个new中打印了文件名和行号,不过细心的你会发现delete时并没有调用重载的placement delete ,这个delete只有在构造对象时抛出了异常才会调用,我们写一个简单的class来看看:
class ThrowExcept { public: ThrowExcept(int v):value_(v){ throw 1;} private: int value_; }; int main(){ ThrowExcept *t; try{ t = new(__FILE__, __LINE__) ThrowExcept(10); }catch(int &e){ printf("catch exception %d ", e); } }
运行结果:
new called at testnew.cpp:53 size=4
operator new called, size=4
delete called at testnew.cpp:53 place=0x9e2e008
operator delete called
catch exception 1
自定义的delete被正常调用,这么做的原因在于如果构造函数抛出异常,系统正常的operator delete并不知道用户自定义的placement new做了什么,自然也不知道怎么去释放。所以如果自己定义placement new, 一定要定义对应的palcement delete,不然可能出现memory leak。
http://en.cppreference.com/w/cpp/memory/new/operator_new
http://en.cppreference.com/w/cpp/language/new
《effective/more effective c++》