关于C++中的new 和 delete
在C++ 中,常用的内存操作符就是new 和 delete,分别负责进行内存的申请和释放。例如对于一个String对象,如果要在堆中申请空间可以写成:
String* pString = new String(“Hello everyong”);
如果使用结束,要对内存进行释放,即使用delete operator:
delete pString;
其实,表面上使用的new 和 delete在C++ 中被称为new operator 和 delete operator. 在进行内部调用的时候,new operator 调用 operator new 进行内存空间的申请,然后再申请的内存空间上调用constructor 进行对象的构造,在新近的标准中,要求如果构造函数构造失败,需要在new operator 结束前对申请的空间进行释放(旧标准没有对此进行规定,应该注意,这可能影响程序设计和程序的兼容性)。
对于delete operator 首先调用对象的析构函数,对对象进行析构,而后调用operator delete释放内存空间。
对于new operator 和 delete operator 它们所做的事情是由语言本身固定下来的,我们无法对其进行修改,但是我们可以对它们调用的函数进行修改,以达到影响其的目的,例如,我们可以对operator new 和 operator delete进行重载(注意:这与new operator 和 delete operator 是不同的)。
在新近的C++标准中,与new operator 相对应的有operator new、operator new[]、placement new、placement new[],声明形式类似如下形式:
void* operator new (size_t size); // operator new ①
void* operator new[](size_t size); // operator new[] ②
void* operator new (size_t size, const std::nothrow_t&); // operator new, but no throw ③
void* operator new[](size_t size, const std::nothrow_t&); // operator new[], but no throw ④
inline void* operator new (size_t size, void* p) // placement new ⑤
{return p;}
inline void* operator new[] (size_t size, void* p) // placement new[] ⑥
{return p;}
以上几种形式中,operator new 和 operator new[]应该归为一类,他们是我们使用最频繁的两种形式,它们真实分配内存空间,并返回空间地址,分配失败抛出std::bad_alloc异常。
operator new 的工作方式类似于:
void* operator new(size_t size)
{
if (size == 0)
{
size = 1; // 将大小为0的情况按照1处理
}
while (1)
{
分配size 大小的内存空间;
if (分配成功)
{
return (指向内存的指针);
}
// 分配失败,调用new_handler
new_handler globalHandler = set_new_handler(0);
set_new_handler(globalHandler);
if (globalHandler)
(*globalHandler)();
else
throw std::bad_alloc; // 如果是nothrow的,应该返回0
}
}
从这段代码中我们发现了一个while(1)循环,跳出循环的条件是:
内存申请成功。
new_handler完成如下事情中的一件:
得到更多的可用内存,--使得下一次内存分配成功。
卸除new_handler,--导致一个std::bad_alloc异常被抛出。
抛出一个std::bad_alloc异常或其派生类的异常(事实上,这个版本的operator new 往往有着异常类型的限定,如果抛出其它类型的异常,将导致十分恶劣的后果—往往是你极不希望的)。
返回失败。
不然,while(1)循环永远也不会停止!
③④两种形式是为了兼容早期编写的C++程序而设计的,那时的标准要求分配失败的情况下,像C中一样返回空指针。String* pString = new String(“Hello everyong”);失败时会使得pString为0.
⑤⑥两种形式比较特殊,他们可以对于已经申请好的内存空间进行对象的构造,例如我们可以这样使用String* pString = new(pToSomeMemoryAddress) String(“Hello everyong”);其中的pToSomeMemoryAddress是指向内存空间的指针。通常使用placement new的地方都后配置对应的空间申请和释放的函数,以防止错误的调用操作符进行错误的操作。
与new operator 对应的是 delete operator
对应于 delete operator 的有:
operator delete、operator delete[]、placement delete、placement delete[],声明形式为:
void operator delete (void* p); // operator delete ①
void operator delete[](void* p); // operator delete[] ②
inline void operator new (void* , void* ) // placement delete ③
{ return;}
inline void operator new[] (void* , void* ) // placement delete[] ④
{ return;}
operator delete允许传入的指针为空,其实现类似于:
void operator delete (void* p)
{
if (p == 0)
{return;}
释放内存空间;
return;
}
从这里你也可以部分了解初始化指针的好处,虽然绝对不是全部。另外如果你已经删除了一部分内存空间,应该将对应的指针指向0,这样,如果某些地方不小心再次删除了这个指针也不会产生什么恶劣后果,如若不然,C++中重复删除了非指向0的指针将产生不可预料的后果。
①②两种形式的operator delete对应于常用的delete operator 和 delete[] operator,形式简单,需要说明的是,因为在新近的标准中加入了构造函数构造失败需要释放先前申请的内存的要求,如果将operator delete或者operator delete[]声明为private 类型的话,将同时导致其对应的new operator和 new[] operator无法通过编译,因为new operator和 new[] operator无法调用operator delete 和 operator delete[],它们已经被声明为private的了—虽然你的编译器可能不是按照这个标准设计的,但是你的程序不应该仅仅通过你现在使用的编译器的编译,你应该同时考虑兼容问题。
③④两种是placement delete 和 placement delete[]的声明形式,从其声明和实现形式可知,它们什么都没有做!它们并没有释放内存!这也再次说明了为什么你应该在使用placement new 和 placement delete 的地方需要同时配备自己写的内存申请和释放程序。
需要说明的是,早期的编译器中并不带有placement 形式的delete operator 和 delete[] operator,对于这种情况你需要先手动调用析构函数,然后再释放内存。
对于数组形式的operator new[] 和 operator delete[] ,有些编译环境并不支持,你无法对其进行重载,在这些编译器下,你只能重载 operator new 和 operator delete,其new[] operator 和 delete[] operator 是通过调用 operator new 和 operator delete 来实现其功能的。这就衍生一个问题:通过重载 operator new 和 operator delete 来实现数组功能—这绝对不是一个好主意,因为一旦你的程序被移植到支持operator new[] 和 operator delete[] 上环境下,你的程序就不会按照你设想的情形工作—new[] operator 和 delete[] operator 是调用operator new[] 和 operator delete[]工作的,而不是你设想的 operator new 和 operator delete!
并不是所有的编译器都对标准进行了良好的支持!
最后想说一下的就是new[] operator 是先将所有需要的内存均申请,而后才调用构造函数;delete[] operator 是先调用析构函数对全部对象均析构后再释放内存的,或许这根你想象的有点不一样~
必须要强调:如果你重载了某种形式的operator new,一定要重载对应的operator delete,不然会发生什么?无法想象。