本文主要讲述C++ new运算符和operator new, placement new之间的种种关联,new的底层实现,以及operator new的重载和一些在内存池,STL中的应用。
一、 new运算符和operator new():
operator new
can be called explicitly as a regular function, but in C++, new
is an operator with a very specific behavior: An expression with the new
operator, first calls function operator new
(i.e., this function) with the size of its type specifier as first argument, and if this is successful, it then automatically initializes or constructs the object (if needed). Finally, the expression evaluates as a pointer to the appropriate type. A* a = new A;
我们知道这里分为两步:1.分配内存,2.调用A()构造对象。事实上,分配内存这一操作就是由operator new(size_t)来完成的,如果类A重载了operator new,那么将调用A::operator new(size_t ),如果没有重载,就调用::operator new(size_t ),全局new操作符由C++默认提供。因此前面的两步也就是:1.调用operator new 2.调用构造函数。这里再一次提出来是因为后面关于这两步会有一些变形,在关于placement new那里会讲到。先举个简单例子:
//平台:Visual Stdio 2008 #include<iostream> class A { public: A() { std::cout<<"call A constructor"<<std::endl; } ~A() { std::cout<<"call A destructor"<<std::endl; } } int _tmain(int argc, _TCHAR* argv[]) { A* a = new A; delete a; system("pause"); return 0; }
下面我们跟踪一下A反汇编代码,由于Debug版本反汇编跳转太多,因此此处通过Release版本在A* a = new A;处设断点反汇编:
A* a = new A; 01301022 push 1 ;不含数据成员的类占用一字节空间,此处压入sizeof(A) 01301024 call operator new (13013C2h) ;调用operator new(size_t size) 01301029 mov esi,eax ;返回值保存到esi 0130102B add esp,4 ;平衡栈 0130102E mov dword ptr [esp+8],esi ; 01301032 mov dword ptr [esp+14h],0 0130103A test esi,esi ;在operator new之后,检查其返回值,如果为空(分配失败),则不调用A()构造函数 0130103C je wmain+62h (1301062h) ;为空 跳过构造函数部分 0130103E mov eax,dword ptr [__imp_std::endl (1302038h)] ;构造函数内部,输出字符串 01301043 mov ecx,dword ptr [__imp_std::cout (1302050h)] 01301049 push eax 0130104A push offset string "call A constructor" (1302134h) 0130104F push ecx 01301050 call std::operator<<<std::char_traits<char> > (13011F0h) 01301055 add esp,8 01301058 mov ecx,eax 0130105A call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (1302040h)] 01301060 jmp wmain+64h (1301064h) ;构造完成,跳过下一句 01301062 xor esi,esi ;将esi置空,这里的esi即为new A的返回值 01301064 mov dword ptr [esp+14h],0FFFFFFFFh delete a; 0130106C test esi,esi ;检查a是否为空 0130106E je wmain+9Bh (130109Bh) ;如果为空,跳过析构函数和operator delete 01301070 mov edx,dword ptr [__imp_std::endl (1302038h)] ;析构函数 输出字符串 01301076 mov eax,dword ptr [__imp_std::cout (1302050h)] 0130107B push edx 0130107C push offset string "call A destructor" (1302148h) 01301081 push eax 01301082 call std::operator<<<std::char_traits<char> > (13011F0h) 01301087 add esp,8 0130108A mov ecx,eax 0130108C call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (1302040h)] 01301092 push esi ;压入a 01301093 call operator delete (13013BCh) ;调用operator delete 01301098 add esp,4
二 operator new的三种形式:
throwing (1) void* operator new (std::size_t size) throw (std::bad_alloc); nothrow (2) void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw(); placement (3) void* operator new (std::size_t size, void* ptr) throw();
A* a = new A; //调用throwing(1) A* a = new(std::nothrow) A; //调用nothrow(2)
#ifndef __PLACEMENT_NEW_INLINE #define __PLACEMENT_NEW_INLINE inline void *__cdecl operator new(size_t, void *_P) {return (_P); } #if _MSC_VER >= 1200 inline void __cdecl operator delete(void *, void *) {return; } #endif #endif
new(p) A(); //也可用A(5)等有参构造函数。
#include <iostream> class A { public: A() { std::cout<<"call A constructor"<<std::endl; } ~A() { std::cout<<"call A destructor"<<std::endl; } }; int _tmain(int argc, _TCHAR* argv[]) { A* p = (A*)::operator new(sizeof(A)); //分配 new(p) A(); //构造 p->~A(); //析构 ::operator delete(p); //释放 system("pause"); return 0; }
error C2273: “函数样式转换”: 位于“->”运算符右边时非法
A* a = (A*)::operator new(sizeof(A)); //分配 010614FE push 1 01061500 call operator new (1061208h) 01061505 add esp,4 01061508 mov dword ptr [a],eax //new(a) A(); //构造 a->A::A(); 0106150B mov ecx,dword ptr [a] 0106150E call operator new (1061285h) a->~A(); //析构 01061513 push 0 01061515 mov ecx,dword ptr [a] 01061518 call A::`scalar deleting destructor' (1061041h) ::operator delete(a); //释放 0106151D mov eax,dword ptr [a] 01061520 push eax 01061521 call operator delete (10610B9h) 01061526 add esp,4
小结:
#include <iostream> class A { public: A() { std::cout<<"call A constructor"<<std::endl; } ~A() { std::cout<<"call A destructor"<<std::endl; } }; int main() { // 方式一 begin // A *a = new A; // delete a; // 方式一 end // 方式二(方式一 的 内部调用过程) begin // new 运算符(1 调用operator new, 2 调用构造函数) A* p = (A*)::operator new(sizeof(A)); //分配内存 (全局 operator new), (A*)w为强制类型转换 new(p) A(); //构造 // delete 运算符(1 调用析构函数, 2 调用operator delete) p->~A(); //析构 ::operator delete(p); //释放 // 方式二 end return 0; }
三 operator new重载:
1.在类中重载operator new
#include <iostream> class A { public: A() { std::cout<<"call A constructor"<<std::endl; } ~A() { std::cout<<"call A destructor"<<std::endl; } void* operator new(size_t size) { std::cout<<"call A::operator new"<<std::endl; return malloc(size); } void* operator new(size_t size, const std::nothrow_t& nothrow_value) { std::cout<<"call A::operator new nothrow"<<std::endl; return malloc(size); } }; int _tmain(int argc, _TCHAR* argv[]) { A* p1 = new A; delete p1; A* p2 = new(std::nothrow) A; delete p2; system("pause"); return 0; }
void* ::operator new(size_t size) { std::cout<<"call global operator new"<<std::endl; return malloc(size); }
error C2660: “A::operator new”: 函数不接受 2 个参数。
void* operator new(size_t size, int x, int y, int z) { std::cout<<"X="<<x<<" Y="<<y<<" Z="<<z<<std::endl; return malloc(size); }
这种重载看起来没有什么大作用,因为它operator new需要完成的任务只是分配内存,但是通过对这类重载的巧妙应用,可以让它在动态分配内存调试和检测中大展身手。这将在后面operator new重载运用技巧中,展现。
2.重载全局operator new
全局operator new的重载和在类中重载并无太大区别,当new A;时,如果类A中没有重载operator new,那么将调用全局operator new函数,如果没有重载全局operator new,最后会调用默认的全局operator new。
3.类中operator new和全局operator new的调用时机
前面已经提到了在new时的调用顺序,但是这里提出来的原因是还存在一个全局的new运算符,也就是::new,这个运算符会直接调用全局operator new,并且也会调用构造函数。这可能让人很犯迷糊,只做了解即可。这里提到的调用时机都是指通过new运算符调用,没有讨论其他情况,比如主动调用。
四 operator new运用技巧和一些实例探索
1.operator new重载运用于调试:
前面提到如何operator new的重载是可以有自定义参数的,那么我们如何利用自定义参数获取更多的信息呢,这里一个很有用的做法就是给operator new添加两个参数:char* file, int line,这两个参数记录new运算符的位置,然后再在new时将文件名和行号传入,这样我们就能在分配内存失败时给出提示:输出文件名和行号。
那么如何获取当前语句所在文件名和行号呢,windows提供两个宏:__FILE__和__LINE__。利用它们可以直接获取到文件名和行号,也就是 new(__FILE__, __LINE__) 由于这些都是不变的,因此可以再定义一个宏:#define new new(__FILE__, __LINE__)。这样我们就只需要定义这个宏,然后重载operator new即可。
源代码如下,这里只是简单输出new的文件名和行号:
//A.h class A { public: A() { std::cout<<"call A constructor"<<std::endl; } ~A() { std::cout<<"call A destructor"<<std::endl; } void* operator new(size_t size, const char* file, int line) { std::cout<<"call A::operator new on file:"<<file<<" line:"<<line<<std::endl; return malloc(size); return NULL; } }; //Test.cpp #include <iostream> #include "A.h" #define new new(__FILE__, __LINE__) int _tmain(int argc, _TCHAR* argv[]) { A* p1 = new A; delete p1; A* p2 = new A; delete p2; system("pause"); return 0; }
程序输出:
注意:需要将类的声明实现与new的使用隔离开来。并且将类头文件放在宏定义之前。否则在类A中的operator new重载中的new会被宏替换,整个函数就变成了:
void* operator new(__FILE__, __LINE__)(size_t size, char* file, int line)
编译器自然会报错。
2.内存池优化
operator new的另一个大用处就是内存池优化,内存池的一个常见策略就是一次性分配一块大的内存作为内存池(buffer或pool),然后重复利用该内存块,每次分配都从内存池中取出,释放则将内存块放回内存池。在我们客户端调用的是new运算符,我们可以改写operator new函数,让它从内存池中取出(当内存池不够时,再从系统堆中一次性分配一块大的),至于构造和析构则在取出的内存上进行,然后再重载operator delete,它将内存块放回内存池。关于内存池和operator new在参考文献中有一篇很好的文章。这里就不累述了。
3.STL中的new
在SGI STL源码中,defalloc.h和stl_construct.h中提供了最简单的空间配置器(allocator)封装,见《STL源码剖析》P48。它将对象的空间分配和构造分离开来,虽然在defalloc.h中仅仅是对::operator new和::operator delete的一层封装,但是它仍然给STL容器提供了更加灵活的接口。SGI STL真正使用的并不是defalloc.h中的分配器,而是stl_alloc.h中的SGI精心打造的"双层级配置器",它将内存池技术演绎得淋漓尽致,值得细细琢磨。顺便提一下,在stl_alloc.h中并没有使用::operator new/delete 而直接使用malloc和free。具体缘由均可参见《STL源码剖析》。
五 delete的使用
delete的使用基本和new一致,包括operator delete的重载方式这些都相似,只不过它的参数是void*,返回值为void。但是有一点需要注意,operator delete的自定义参数重载并不能手动调用。比如:
void* operator new(size_t size, int x) { cout<<" x = "<<x<<endl; return malloc(size); } void operator delete(void* p, int x) { cout<<" x = "<<x<<endl; free(p); }
A* p = new(3) A;//Ok delete(3) p;//error C2541: “delete”: 不能删除不是指针的对象
那么重载operator delete有什么作用?如何调用?事实上以上自定义参数operator delete 只在一种情况下被调用:当new运算符抛出异常时。
可以这样理解,只有在new运算符中,编译器才知道你调用的operator new形式,然后它会调用对应的operator delete。一旦出了new运算符,编译器对于你自定义的new将一无所知,因此它只会按照你指定的delete运算符形式来调用operator delete,而至于为什么不能指定调用自定义delete(也就是只能老老实实delete p),这个就不知道了。
细心观察的话,上面operator new用于调试的例子代码中,由于我们没有给出operator new对应的operator delete。在VS2008下会有如下警告:
warning C4291: “void *A::operator new(size_t,const char *,int)”: 未找到匹配的删除运算符;如果初始化引发异常,则不会释放内存。
六 关于new和内存分配的其他
1.set_new_handler
还有一些零散的东西没有介绍到,比如set_new_handler可以在malloc(需要调用set_new_mode(1))或operator new内存分配失败时指定一个入口函数new_handler,这个函数完成自定义处理(继续尝试分配,抛出异常,或终止程序),如果new_handler返回,那么系统将继续尝试分配内存,如果失败,将继续重复调用它,直到内存分配完毕或new_handler不再返回(抛出异常,终止)。下面这段程序完成这个测试:
#include <iostream> #include <new.h>// 使用_set_new_mode和set_new_handler void nomem_handler() { std::cout<<"call nomem_handler"<<std::endl; } int main() { _set_new_mode(1); //使new_handler有效 set_new_handler(nomem_handler);//指定入口函数 函数原型void f(); std::cout<<"try to alloc 2GB memory...."<<std::endl; char* a = (char*)malloc(2*1024*1024*1024); if(a) std::cout<<"ok...I got it"<<std::endl; free(a); system("pause"); }
程序运行后会一直输出call nomem_handler 因为函数里面只是简单输出,返回,系统尝试分配失败后,调用nomem_handler函数,由于该函数并没有起到实际作用(让可分配内存增大),因此返回后系统再次尝试分配失败,再调用nomem_handler,循环下去。
2.new分配数组
A* p = new A[3];中,会直接调用全局的operator new[](size_t size),而不管A中是否有operator new[]的重载。而delete[]p却会优先调用A::operator delete[](void*)(如果A中有重载)。另外还要注意的是,在operator new[](size_t size)中传入的并不是sizeof(A)*3。而要在对象数组的大小上加上一个额外数据,用于编译器区分对象数组指针和对象指针以及对象数组大小。在VS2008下这个额外数据占4个字节,一个int大小。测试代码如下:
//A.h class A { public: A() { std::cout<<"call A constructor"<<std::endl; } ~A() { std::cout<<"call A destructor"<<std::endl; } void* operator new(size_t size) { std::cout<<"call A::operator new[] size:"<<size<<std::endl; return malloc(size); } void operator delete[](void* p) { std::cout<<"call A::operator delete[]"<<std::endl; free(p); } void operator delete(void* p) { free(p); } }; //Test.cpp #include <iostream> #include "A.h" void* operator new[](size_t size) { std::cout<<"call global new[] size: "<<size<<std::endl; return malloc(size); } void operator delete[](void* p) { std::cout<<"call global delete[] "<<std::endl; } int _tmain(int argc, _TCHAR* argv[]) { std::cout<<"sizeof A "<<sizeof(A)<<std::endl; A* p1 = new A[3]; delete []p1; system("pause"); return 0; }
参考文献:
1.http://www.cplusplus.com/reference/new/operator%20new/?kw=operator% operator new的三种形式
2.http://www.relisoft.com/book/tech/9new.html c++ operator new重载和内存池技术
3.《STL源码剖析》 空间配置器
转载请注明出处:http://blog.csdn.net/wudaijun/article/details/9273339
【下部分转自】http://blog.csdn.net/shandianling/article/details/8005657
1. plain new 普通new
void*operator new(std::size_t)throw(std::bad_alloc); void operator delete( void *) throw();
该运算符在分配失败时将抛出异常,而非返回NULL。使用时要包含 <new>头文件。正常使用new,但要配以异常处理。如:
char *getMemory(unsigned long size) { char * p = new char[size]; return p; } void main(void ) { try{ char * p = getMemory(1000000);//可能发生异常 // ... delete [ ] p; } catch(const std::bad_alloc & ex) { cout < <ex.what(); } }
2.nothrow new 不抛掷异常new
void*operator new(std::size_t,const std::nothrow_t & )throw(); void operator delete( void *) throw();
该运算符在分配失败时不抛出异常,而是返回NULL。使用时要包含 <new>头文件。
该函数的第2形参是 struct nothrow_t { };它是个全局常对象 const nothrow_t nothrow; 用来作为 new 运算符的标志,以区别前一个new.
3.placement new 放置new
void*operator new(std::size_t ,void *); void operator delete( void * ,void *);
该运算符是在已分配的内存上重新构造对象,因为不分配内存,所以不必担心分配失败。唯一的工作是调用构造函数。要包含 <new>头文件。
# include <new> # include <iostream> void main() { using namespace std; char * p = new(nothrow) char [4]; if (p == NULL) { cout < <“allocte failed” < <endl; exit( -1 ); } // ... long * q = new(p)long(1000); delete [ ]p; //只释放 p,不要用q释放。 }
p和q仅仅是首址相同,所构建的对象可以类型不同。所“放置”的空间应小于原空间,以防不测。当”放置new”超过了申请的范围,Debug版下会挂机,但Release版竟然能运行而不出错!
该运算符的作用是:只要第一次分配成功,不再担心分配失败。
# include <new> # include <iostream> void main() { using namespace std; char * p = new(nothrow) char [100]; if (p == NULL) { cout < <“allocte failed” < <endl; exit( -1 ); } long * q1 = new(p)long(100); // 使用q1 ... int * q2 = new(p) int[100/sizeof(int) ]; // 使用q2 ... ADT * q3 = new(p) ADT[100/sizeof(ADT) ]; // 使用q3 然后释放对象 ... delete [ ]p; //只释放空间,不再析构对象。 }
注意:使用该运算符构造的对象或数组,一定要显式调用析构函数,不可用delete代替析构,因为placement new 的对象的大小不再与原空间相同。
# include <new> # include <iostream> void main() { using namespace std; char * p = new(nothrow) char [sizeof(ADT)+2]; if (p == NULL) { cout < <“allocte failed” < <endl; exit( -1 ); } // ... ADT * q = new(p) ADT; // ... // delete q; // 错误 q-> ADT::~ADT(); //显式调用析构函数,仅释放对象 delete [ ]p; //最后,再用原指针来释放内存. }
placement new 的主要用途就是可以反复使用一块已申请成功的内存空间。这样可以避免申请失败的徒劳,又可以避免使用后的释放。
特别要注意的是对于 placement new 绝不可以调用的delete, 因为该new只是使用别人替它申请的地方(只是个租房户,不是房主。无权将房子卖掉)。释放内存是nothrow new的事,即要使用原来的指针释放内存
/*********************************************************/
定位new运算符
通常,new 从堆中分配内存,但它还有另一种称为 定位(placement)new 运算符,它可以让我们指定要使用的位置。可以通过这个特性来设置内存管理规程,处理需要通过特定地址进行访问的硬件或在特定位置创建对象。
要使用定位 new 特性,需要包含头文件 new。使用定位 new 运算符时,变量后面可以有方括号,也可以没有。下面代码中同时使用了传统的 new 运算 符和定位 new 运算符:
// newplace.cpp -- using placement new
#include <iostream>
#include <new> // 使用定位 new 所需要的头文件
const int BUF = 512;
const int N = 5;
char buffer[BUF]; // 数组缓冲区
int main()
{
using namespace std;
double *pd1, *pd2;
int i;
cout << "Calling new and placement new:
";
pd1 = new double[N]; // 从堆中分配
pd2 = new (buffer) double[N]; // 使用缓冲数组
for (i = 0; i < N; i++)
pd2[i] = pd1[i] = 1000 + 20.0 * i;
cout << "Memory addresses:
" << " heap: " << pd1
<< " static: " << (void *) buffer <<endl;
cout << "Memory contents:
";
for (i = 0; i < N; i++)
{
cout << pd1[i] << " at " << &pd1[i] << "; ";
cout << pd2[i] << " at " << &pd2[i] << endl;
}
cout << "
Calling new and placement new a second time:
";
double *pd3, *pd4;
pd3= new double[N]; // 从堆中分配一块新的内存
pd4 = new (buffer) double[N]; // 覆盖了原来的数据
for (i = 0; i < N; i++)
pd4[i] = pd3[i] = 1000 + 40.0 * i;
cout << "Memory contents:
";
for (i = 0; i < N; i++)
{
cout << pd3[i] << " at " << &pd3[i] << "; ";
cout << pd4[i] << " at " << &pd4[i] << endl;
}
cout << "
Calling new and placement new a third time:
";
delete [] pd1;
pd1= new double[N];
pd2 = new (buffer + N * sizeof(double)) double[N];
for (i = 0; i < N; i++)
pd2[i] = pd1[i] = 1000 + 60.0 * i;
cout << "Memory contents:
";
for (i = 0; i < N; i++)
{
cout << pd1[i] << " at " << &pd1[i] << "; ";
cout << pd2[i] << " at " << &pd2[i] << endl;
}
delete [] pd1;
delete [] pd3;
// cin.get();
return 0;
}
程序输出:
Calling new and placement new: Memory addresses: heap: 001D7E98 static: 0037A138 Memory contents: 1000 at 001D7E98; 1000 at 0037A138 1020 at 001D7EA0; 1020 at 0037A140 1040 at 001D7EA8; 1040 at 0037A148 1060 at 001D7EB0; 1060 at 0037A150 1080 at 001D7EB8; 1080 at 0037A158 Calling new and placement new a second time: Memory contents: 1000 at 001D8840; 1000 at 0037A138 1040 at 001D8848; 1040 at 0037A140 1080 at 001D8850; 1080 at 0037A148 1120 at 001D8858; 1120 at 0037A150 1160 at 001D8860; 1160 at 0037A158 Calling new and placement new a third time: Memory contents: 1000 at 001D7E98; 1000 at 0037A160 1060 at 001D7EA0; 1060 at 0037A168 1120 at 001D7EA8; 1120 at 0037A170 1180 at 001D7EB0; 1180 at 0037A178 1240 at 001D7EB8; 1240 at 0037A180
定位new 的一个主要特征就是在先分配好的缓冲区中划分出一部分用作我们自己的内存规划。如同上面,先分配好一段共 512 字节大小的 buffer 缓冲区,然后将 5*double (40个字节)大小的区域用来存放 double 数组。这个 buffer缓冲区域是由全局数组变量 buffer[512] 定义出来的,这块区域位于静态数据区中,当然这不是必须的,这块缓冲区域同样可以位于栈中,堆中都没问题,关键是它是事先被划分好的,然后我们再在里边划分出一块为己用,这就达到了”定位“的目的。
需要注意 定位new 的使用语法, new (buffer) double[N]; 表 示将拥有 N 个 double 元素的数组放在 buffer缓冲区中,并且是从开始存放,这也是上面程序中第 1 种情况所演示的。在第 2 种情况里,再次存放了另外一个数组,也是从开始处存放,这会覆盖原来的数据。第 3 种情况,并不是从开始处存放的,而是从一个偏移处开始存放,这样可以避免覆盖掉之前的数据。
定位new 应用于对象
上面的定位 new 运算符应用于内置的数据类型(如 double),它也可以应用于对象,此时情况有些不同:
#include <iostream>
#include <string>
#include <new>
using namespace std;
const int BUF = 512;
class JustTesting
{
private:
string words;
int number;
public:
JustTesting(const string & s = "Just Testing", int n = 0)
{words = s; number = n; cout << words << " constructed
"; }
~JustTesting() { cout << words << " destroyed
";}
void Show() const { cout << words << ", " << number << endl;}
};
int main()
{
char * buffer = new char[BUF]; // 分配一块缓冲区
JustTesting *pc1, *pc2;
pc1 = new (buffer) JustTesting; // 将对象放入 buffer[] 中
pc2 = new JustTesting("Heap1", 20); // 将对象放到堆分配的内存中
cout << "Memory block addresses:
" << "buffer: "
<< (void *) buffer << " heap: " << pc2 <<endl;
cout << "Memory contents:
";
cout << pc1 << ": ";
pc1->Show();
cout << pc2 << ": ";
pc2->Show();
delete pc1;
JustTesting *pc3, *pc4;
pc3 = new (buffer) JustTesting("Bad Idea", 6); //之前的被覆盖了
pc4 = new JustTesting("Heap2", 10);
cout << "Memory contents:
";
cout << pc3 << ": ";
pc3->Show();
cout << pc4 << ": ";
pc4->Show();
delete pc2; // 释放 Heap1
delete pc4; // 释放 Heap2
delete [] buffer; // 释放 buffer
cout << "Done
";
// std::cin.get();
return 0;
}
Just Testing constructed Heap1 constructed Memory block addresses: buffer: 004D8800 heap: 00658EF0 Memory contents: 004D8800: Just Testing, 0 00658EF0: Heap1, 20 Bad Idea constructed Heap2 constructed Memory contents: 004D8800: Bad Idea, 6 004D8AA0: Heap2, 10 Heap1 destroyed Heap2 destroyed Done
pc1 = new (buffer) JustTesting; pc3 = new (buffer + sizeof (JustTesting)) JustTesting("Better Idea", 6);
解决上述没有调用析构函数问题的方法是,显式为定位 new 运算符创建的对象调用析构函数,这也是显示调用析构函数的少数几种情形之一。显示调用析构函数,必须指定要销毁的对象。由于有指向对象的指针,因此可以如下调用析构函数:
pc3->~JustTesting();
pc1->~JustTesting();
加入合适的delete和显式的析构函数调用,能够解决这样的问题,但是需要注意的一点是正确的删除顺序。对于使用定位new运算符创建的对象,应以与创建顺序相反的顺序进行删除。原因在于,晚创建的对象可能依赖于早创建的对象。另外,仅当所有对象都被销毁后,才能释放用于存储这些对象的缓冲区。
#include <iostream>
#include <string>
#include <new>
using namespace std;
const int BUF = 512;
class JustTesting
{
private:
string words;
int number;
public:
JustTesting(const string & s = "Just Testing", int n = 0)
{words = s; number = n; cout << words << " constructed
"; }
~JustTesting() { cout << words << " destroyed
";}
void Show() const { cout << words << ", " << number << endl;}
};
int main()
{
// char * buffer = new char[BUF]; // 分配一块缓冲区
char buffer[BUF];
JustTesting *pc1, *pc2;
pc1 = new (buffer) JustTesting; // 将对象放入 buffer[] 中
pc2 = new JustTesting("Heap1", 20); // 将对象放到堆分配的内存中
cout << "Memory block addresses:
" << "buffer: "
<< (void *) buffer << " heap: " << pc2 <<endl;
cout << "Memory contents:
";
cout << pc1 << ": ";
pc1->Show();
cout << pc2 << ": ";
pc2->Show();
JustTesting *pc3, *pc4;
// 修正重叠问题
pc3 = new (buffer + sizeof (JustTesting))
JustTesting("Better Idea", 6);
pc4 = new JustTesting("Heap2", 10);
cout << "Memory contents:
";
cout << pc3 << ": ";
pc3->Show();
cout << pc4 << ": ";
pc4->Show();
delete pc2; // 释放 Heap1
delete pc4; // 释放 Heap2
// 显示销毁由定位 new 创建的对象,注意调用的顺序
pc3->~JustTesting(); // 销毁由 pc3 指向的对象
pc1->~JustTesting(); // 销毁由 pc1 指向的对象
//delete [] buffer; // 释放 buffer
return 0;
}