关键字 new 和 delete 的各种可能用途似乎造成了相当大的混乱。在 C++ 中构造动态对象总是有两个阶段:原始内存的分配和在分配的内存区域中构造新对象。在对象生命周期的另一边是对象的销毁和对象所在的内存位置的释放。
这两个步骤通常由单个 C++ 语句执行。
MyObject* ObjPtr = new MyObject;
//...
delete MyObject;
除了上述之外,您还可以使用 C++ 原始内存分配函数operator new
和operator delete
显式构造(通过放置new
)和销毁来执行等效步骤。
void* MemoryPtr = ::operator new( sizeof(MyObject) );
MyObject* ObjPtr = new (MemoryPtr) MyObject;
// ...
ObjPtr->~MyObject();
::operator delete( MemoryPtr );
请注意如何不涉及强制转换,并且在分配的内存区域中只构造了一种类型的对象。使用类似new char[N]
的方法来分配原始内存在技术上是不正确的,因为从逻辑上讲,char
对象是在新分配的内存中创建的。我不知道在任何情况下它不能“正常工作”,但它模糊了原始内存分配和对象创建之间的区别,所以我建议不要这样做。
在这种特殊情况下,分离出两个步骤没有任何好处,delete
但您确实需要手动控制初始分配。上面的代码在“一切正常”的情况下工作,但在构造函数MyObject
抛出异常的情况下会泄漏原始内存。虽然这可以在分配点通过异常处理程序捕获和解决,但提供自定义运算符 new 可能更简洁,以便可以通过放置 new 表达式处理完整的构造。
class MyObject
{
void* operator new( std::size_t rqsize, std::size_t padding )
{
return ::operator new( rqsize + padding );
}
// Usual (non-placement) delete
// We need to define this as our placement operator delete
// function happens to have one of the allowed signatures for
// a non-placement operator delete
void operator delete( void* p )
{
::operator delete( p );
}
// Placement operator delete
void operator delete( void* p, std::size_t )
{
::operator delete( p );
}
};
这里有几个微妙的点。我们定义了一个新的类放置,以便我们可以为类实例分配足够的内存以及一些用户可指定的填充。因为我们这样做,所以我们需要提供匹配的放置删除,以便如果内存分配成功但构造失败,分配的内存会自动释放。不幸的是,我们的展示位置删除的签名与非展示位置删除的两个允许签名之一匹配,因此我们需要提供另一种形式的非展示位置删除,以便我们真正的展示位置删除被视为展示位置删除。(我们可以通过在placement new 和placement delete 中添加一个额外的虚拟参数来解决这个问题,但这需要在所有调用站点上进行额外的工作。)
// Called in one step like so:
MyObject* ObjectPtr = new (padding) MyObject;
使用单个新表达式,我们现在可以保证如果新表达式的任何部分抛出,内存不会泄漏。
在对象生命周期的另一端,因为我们定义了operator delete(即使我们没有,对象的内存无论如何最初来自全局operator new),下面是销毁动态创建的对象的正确方法.
delete ObjectPtr;
概括!
看没有演员表!operator new
并且operator delete
处理原始内存,placement new 可以在原始内存中构造对象。从 avoid*
到对象指针的显式转换通常是逻辑错误的标志,即使它确实“正常工作”。
我们完全忽略了 new[] 和 delete[]。这些可变大小的对象在任何情况下都不会在数组中工作。
放置 new 允许新表达式不泄漏,新表达式仍计算为指向需要销毁的对象和需要解除分配的内存的指针。使用某种类型的智能指针可能有助于防止其他类型的泄漏。从好的方面来说,我们让 plaindelete
成为正确的方法,所以大多数标准智能指针都可以工作。