我们需要将一些堆存储的分配和初始化分开。不幸的是,客户端代码使用delete p;
来删除指针。如果我们可以控制删除,我们可以使用 进行分配::operator new (sizeof(T), std::align_val_t(alignof(T)))
,然后使用放置new
和相应的全局::operator delete
。但是(如果我理解正确的话)delete
可能会调用T::operator delete
,可能需要来自 的分配T::operator new
,并且 的不同可能参数之间的优先顺序很复杂T::operator new
,T::operator delete
如果有多个重载的话。
是否有类似的东西std::allocate_as_if_by_new<T>()
可以执行完全相同的分配new T(...)
,但无需初始化,或者至少保证(初始化后)delete p;
将正确地释放它?
不。你能做的最好的事情就是使用类似句柄习语的东西。
句柄习惯用法是当您传递一个不是指向实际数据的真实指针的值时。您可以通过让句柄具有您控制的析构函数来允许删除它。
new Forwarder{ pRealObject }
然后从您的 API返回。它们对
delete
对象Forwarder
进行适当的清理,以处理您实际的底层对象pImpl
。如果它有一个虚拟函数表,您的Forwarder
实现只会将所有内容转移到混乱的pImpl
地方。如果它没有虚函数表(比如,ADT 中的自由函数),您可以教那些 ADT 转换参数
Forwarder*
然后使用pImpl
。Interface
如果对公共类和内部类使用不同的类,可以避免pImpl
调用需要使用内部类型的句柄的公共 API,反之亦然;这需要一些代码重复或一些模板愚蠢的行为。在更像 C 的环境中,句柄用法如下:
从创建代码中返回一个指向不透明结构的指针。
简短回答:不。
长答案:
delete ptr;
标准中涵盖了哪些指针是单对象删除表达式的有效操作数(例如)expr.delete
:因此,如果您想要
delete ptr;
被定义,它必须是:T* ptr = new T;
)(或者如果
T
是类类型,则指向T
的基类之一的指针也是允许的)请注意,这会立即排除任何恶作剧,例如
operator new
手动调用然后使用 placement-new 在该内存中创建对象。您可以考虑以下几种替代方案:
1. 使用分配器
分配器将分配和构造分为不同的步骤(以及销毁和释放)。
因此假设您可以更改代码以使用
std::allocator
(或任何其他满足分配器完整性要求的分配器),那么就可以分配T
而不初始化它们:或者假设你用unique-ptr标记了你的问题:
请注意,您必须确保始终
T
在该存储中创建一个新对象,否则std_allocator_deleter
删除器将出现未定义的行为。2. 不要使用删除表达式
一个简单的解决方法就是不使用 delete 表达式。
如果你显式调用析构函数,然后调用
operator delete
你的程序,那么也会表现良好:3. 使用 new 表达式创建一个对象并替换它
如果您的
T
类型是廉价的默认可构造类型(或者您可以修改它们),那么您可以使用普通的 new 表达式并立即调用其析构函数:请注意,在将新对象传递给 delete 表达式之前,必须始终将新
T
对象放置在返回的指针处allocate_uninitialized()
,否则行为未定义。