我正在尝试用我自己的分配器替换new / delete。所以,重写新的和删除 - 非常满意。看起来像这样...
void* operator new( size_t size, Allocator* a )
{
return a->Alloc( size );
}
template<class T> inline void MyDelete( T* p, Allocator* a )
{
if( p )
{
p->~T();
a->Free( p );
}
}
C ++语言指定,对于放置删除,您必须显式调用〜dtor。编译器不会为您执行此操作。这是一个模板化的操作符删除还是显示的显式函数。
请参阅http://www2.research.att.com/~bs/bs_faq2.html#placement-delete
问题是 - 我怎样才能让这个用于数组删除[]?我知道我需要遍历数组并自己调用~dtor。因此我需要数组的大小,
为清晰起见而编辑
我可以存储此信息或从块大小推断出来。然而,问题是编译器(MSVC v9)做了不同的事情,如果我分配一个带有析构函数的对象数组与没有析构函数的数组相比,即如果有一个dtor它将分配额外的4个字节。这是因为标准delete []的编译器需要执行相同的操作,并且可以为delete []配对相应的代码。
然而,在我自己的“placement”中删除[]我无法知道编译器做了什么,或者如果类有dtor,则在编译时安全地确定。
E.g。
char buf[ 1000 ];
MyClass* pA = new( buf ) MyClass[ 5 ];
这里pA的值是buf + 4,如果存在~MyClass()并且分配的内存量是sizeof(MyClass)* 5 + 4.但是如果没有dtor那么pA == buf和金额分配的内存是sizeof(MyClass)* 5.
所以我的问题是 - 这种行为是一种语言标准,并且在编译器中是一致的,还是MSVC特有的?还有其他人对这个问题有一个很好的解决方案吗?我想唯一的选择是不使用new []并自己进行构造,这很好但是调用代码语法有点不寻常..或者强制每个类都有一个析构函数。
答案 0 :(得分:4)
如有疑问请咨询专家:
http://www.stroustrup.com/bs_faq2.html#placement-delete
但是我们怎样才能正确删除这些对象呢?没有内置的“放置删除”来匹配放置新的原因是没有通用的方法来确保它将被正确使用。 C ++类型系统中的任何内容都不允许我们推断出p1指向Arena a1中分配的对象。可以将指向任何地方分配的X的指针分配给p1。
链接的其余部分描述了如何纠正这种情况。
答案 1 :(得分:3)
简答:
此用法没有直接支持。如果使用不同的签名重载new,编译器会认为它是new(不是placement new)的重载并添加了自己的簿记代码。没有办法(我可以找到)对编译器说“解开你的簿记,并调用我的删除超载来匹配这个签名” - 它只会在调用void operator delete(void* p)
时插入代码来解开簿记或者void operator delete[](void* p)
。
如果您使用新签名覆盖new,编译器会喜欢您在新的例外情况下定义具有匹配签名的删除 - 这是唯一一次使用它。
在不可调用的意义上没有放置删除,但是在异常的情况下定义(无所事事)。
答案很长:
这个主题提出了一些有趣的观点:
void* operator new[](size_t sz, Allocator* a)
过载究竟是什么?void operator delete[](void* p, Allocator* a)
?第1点:关于超载放置新品的讨论。鉴于编译器正在插入簿记代码,它必须认为void* operator new[](size_t sz, Allocator* a)
声明了(非位置)新的重载。它永远不会为新的展示位置插入记账代码,因为新的展示位置是您自己处理它。
第2点:R.E。 “没有放置删除这样的东西”,你会发现一些看起来非常像它(并且如此评论)的东西,例如VS2k8新标题。它只是在放置new期间发生异常的情况下使用的存根。但是,您似乎无法以有意义的方式调用展示位置删除。
第3点:如果有办法,我找不到它。这是问题的核心。
就问题的实际解决方案而言,它似乎是一个半身像。
例如:
//intention: user provides memory pool, compiler works out how many bytes required
//and does its own book-keeping, as it would for a void* operator new[](size_t sz) overload
//calling syntax: CObj* pMyArr = new(pMyMemPool) CObj[20];
void* operator new[](size_t sz, IAlloc* pMemPool)
{ return pMemPool->alloc(sz); }
//problem: don't know the syntax to call this!
//e.g. delete[](pMyMemPool) pMyArr is syntax error
void* operator delete[](void* p, IAlloc* pMemPool)
{ return pMemPool->free(p); }
//nb: can be called as operator delete(pMyArr, pMyMemPool);
//but compiler does not finish its book-keeping or call dtors for you in that case.
请注意,这种非对称性存在于非阵列新&amp;也删除。但是,因为(根据经验),所讨论的编译器没有额外的簿记,所以可以全部工作。再说一次,如果这是标准的,我不知道。
void* operator new(size_t sz, IAlloc* pMemPool)
{ return pMemPool->alloc(sz); }
//don't know syntax to get this called by compiler!
void operator delete(void* p, IAlloc* pMemPool)
{ pMemPool->free(p); }
//is ok though, can work around
template<class T> void tdelete(void* p, IAlloc* pMemPool)
{
//no problems, p points straight at object
p->~T();
operator delete(p, pMemPool);
//OR just
pMemPool->free(p);
}
void* operator new[](size_t sz, IAlloc* pMemPool)
{ return pMemPool->alloc(sz); }
//again, don't know syntax to end up here.
void operator delete[](void* p, IAlloc* pMemPool)
{ pMemPool->free(p); }
//can't work around this time!
template<class T> void tarrdelete(void* p, IAlloc* pMemPool)
{
//problem 1: how many to dtor?
for(int i=0; i<???; ++i)
{ reinterpret_cast<T*>(p+i)->~T(); }
//problem 2: p points at first element in array. this is not always the address
//that was allocated originally.
pMemPool->free(?);
//as already explained by OP, no way to tell if p is address allocated or
//address allocated+4 bytes, or something else altogether. this means no way to know what address to un-alloc or how many dtors to call.
}
最后,我将陈述obvs。 - 没有扩展参数列表的重载确实有效:
//sz may include extra for book-keeping
void* operator new[](size_t sz)
{ return GAlloc->alloc(sz); }
//works fine, compiler handled book-keeping and p is the pointer you allocated
void operator delete[](void* p)
{ return GAlloc->free(p); }
摘要:是否存在允许使用扩展参数列表调用delete重载的语法,并启用编译器“magic”。或者,有没有办法通过覆盖添加参数到新的位置?
疑似答案:否。
推论:您不能完全自由地偏离6个内置的新签名。这样做会导致新的过载,编译器生成的簿记,但无法访问相应的删除以解除对记账的保护。
警告:您可以偏离内置签名,但只能注入您在删除时不需要再次处理的代码(例如,检测)。如果您进入分配的void* operator new(size_t s)
版本,则删除仍将正常工作。
(有些事实陈述来自调试器中的实验,可能只适用于MSVC8(cl9).OP坐在我旁边的桌子上。)
答案 2 :(得分:1)
没有“放置删除”这样的术语。正如您所说,如果您使用placement new分配内容,那么当需要解除分配时,您需要手动调用析构函数,然后还要处理为新位置分配的实际内存缓冲区。
但是,如果不手动跟踪自己的分配大小,就不可能做到这一点。原因是“放置新”的整个要点是将分配与对象初始化分离。因此,对于placement new,分配内存缓冲区的行为与构造或破坏任何可能(或可能不)发现自己生活在缓冲区中的对象完全分开。
所以,例如,如果你分配一些缓冲区,比如char buf[1000]
,那么你使用placement new来构造该缓冲区中的Foo
个对象数组,其中C ++应该存储数组尺码信息?它不会将它存储在缓冲区中,因为它不知道你想用缓冲区做什么。因此,您需要记录每个分配的大小,然后将其与释放正确耦合。
答案 3 :(得分:0)
您可以在分配器中查看指针,找出记账中的大小,并使用sizeof T计算元素数量。