假设我想将一个对象放在“堆”上,但我需要在它之后分配一些额外的内存。
我的理解是,以下是符合标准的方法:
(暂时假设没有任何内容覆盖
new
,并且 SomeClass
的析构函数不会抛出)
#include <new>
void f(std::size_t n) {
void* buf = ::operator new(sizeof(SomeClass) + n);
SomeClass* obj = new(buf) SomeClass;
// Do stuff with obj
obj->~SomeClass();
::operator delete(buf);
}
但是假设我不想一直保留
buf
变量。也许我在一个函数中创建了该对象,然后在其他函数中销毁它,并且我不方便继续与 buf
一起传递 obj
。这样做是否有效:
#include <new>
void f(std::size_t n) {
void* buf = ::operator new(sizeof(SomeClass) + n);
SomeClass* obj = new(buf) SomeClass;
// Do stuff with obj
obj->~SomeClass();
::operator delete(static_cast<void*>(obj));
}
我无法从参考中最终确定由placement new返回的指针,如果转换为
void*
,是否被认为是由operator new
返回的“相同”指针。
最后,这样的事情是否有可能有效:
#include <new>
void f(std::size_t n) {
void* buf = ::operator new(sizeof(SomeClass) + n);
SomeClass* obj = new(buf) SomeClass;
// Do stuff with obj
delete obj;
}
我认为它无效,但从技术上讲,
obj
是由new
表达式返回的,并且它位于由new
运算符分配的内存中,所以也许?
编辑:我刚刚想到,另一种方法是为
operator new
添加覆盖,然后在新的放置中使用它?换句话说,这样的东西有效吗:
#include <new>
enum class ExtraSpace : std::size_t {};
void* operator new(std::size_t size, ExtraSpace n) {
return ::operator new(size + static_cast<std::size_t>(n));
}
void f(std::size_t n) {
SomeClass* obj = new(ExtraSpace(n)) SomeClass;
// Do stuff with obj
delete obj;
}
(我正在使用 C++14,但也有兴趣了解其他标准的任何差异)
void* buf = ::operator new(sizeof(SomeClass) + n);
SomeClass* obj = new(buf) SomeClass;
delete obj;
可能是C++14中未定义的行为([expr.delete]p10的措辞由CWG1788的决议修改):
如果释放函数查找既找到仅具有指针参数的普通释放函数,又找到具有指针参数和大小参数的普通释放函数,则按如下方式选择要调用的函数:
- [对于数组
,...]delete[]
- 否则,无法指定选择两个释放函数中的哪一个。
如果选择了未调整大小的
operator delete((void*) obj)
,那么就可以了。但是,如果选择了大小合适的 operator delete((void*) obj, sizeof(SomeClass))
,则会出现未定义的行为,因为该大小与传递给 operator new
的大小不同。
GCC 默认调用已调整大小的
operator delete(void*, std::size_t)
,Clang 默认调用未调整大小的 operator delete(void*)
。
至于
void* buf
变量,void* operator new(std::size_t, void* ptr)
被专门定义为返回 ptr
不变([new.delete.placement]p2)。所以 obj
和 buf
具有相同的值(在 C++17 中指向相同的地址),因此 operator delete(static_cast<void*>(obj))
相当于 operator delete(buf)
。