全局"展示位置"删除[]

Gwa*_*edd 9 c++ visual-c++

我试图用我自己的分配器替换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 );
    }
}
Run Code Online (Sandbox Code Playgroud)

C++语言指定,对于放置删除,您必须显式调用~dtor.编译器不会为您执行此操作.这是一个模板化操作符删除还是显式函数如图所示.

http://www2.research.att.com/~bs/bs_faq2.html#placement-delete

问题是 - 我怎样才能使这个数组删除[]?我知道我需要遍历数组并自己调用~dtor.因此我需要数组的大小,

编辑清晰

我可以存储此信息或从块大小推断它.然而,问题是编译器(MSVC v9)做了不同的事情,如果我分配一个带有析构函数的对象数组与没有析构函数的对象相比,即如果有一个dtor它将分配额外的4个字节.这是因为标准delete []的编译器需要做同样的事情,并且可以为delete []配对相应的代码.

但是在我自己的"placement"中删除[]我无法知道编译器做了什么,或者如果类有一个dtor,则在编译时安全地确定.

例如

char buf[ 1000 ];

MyClass* pA = new( buf ) MyClass[ 5 ];
Run Code Online (Sandbox Code Playgroud)

这里pA的值是buf + 4,如果存在~MyClass()并且分配的内存量是sizeof(MyClass)*5 + 4.但是如果没有dtor那么pA == buf并且分配的内存量是sizeof(MyClass)*5.

所以我的问题是 - 这种行为是一种语言标准,并且在编译器中是一致的,还是MSVC特有的?还有其他人对这个问题有一个很好的解决方案吗?我想唯一的选择是不使用new []并自己进行构造,这很好,但调用代码语法有点不寻常..或强制每个类都有一个析构函数.

小智 3

简短回答:

没有对此用法的直接支持。如果您使用不同的签名重载 new,编译器会认为它是 new 的重载(而不是放置 new)并添加自己的簿记代码。没有办法(我可以找到)对编译器说“展开你的簿记,并调用与此签名匹配的删除重载” - 它只会在调用 或 时插入代码来展开void operator delete(void* p)簿记void operator delete[](void* p)

如果您确实使用新签名覆盖 new,编译器会喜欢您定义具有匹配签名的删除,以防在 new 期间出现异常 - 这是唯一一次使用它。

没有放置删除,因为它是不可调用的,但它是在发生异常时定义的(不执行任何操作)。

长答案:

这个话题提出了一些有趣的观点:

  1. 到底什么是void* operator new[](size_t sz, Allocator* a)超载?
  2. 是否存在“展示位置删除”。
  3. 如何void operator delete[](void* p, Allocator* a)以编译器插入其簿记终结的方式调用 , ?

第一点:关于重载放置新内容的讨论很多。鉴于编译器正在插入簿记代码,它必须认为这void* operator new[](size_t sz, Allocator* a)声明了(非放置)new 的重载。它永远不会为placement new插入记账代码,因为placement new的重点是你自己处理它。

第2点:RE“没有放置删除之类的东西”,您会在例如VS2k8新标头中找到看起来非常相似的东西(并这样注释)。它只是一个存根,用于在放置 new 期间发生异常的情况。然而,您确实无法以有意义的方式调用放置删除。

第三点:即使有办法,我也找不到。这是问题的核心。

就问题的实际解决方案而言,它似乎是失败的。

例如:

//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.
Run Code Online (Sandbox Code Playgroud)

请注意,这种不对称性也存在于非数组的新建和删除中。然而,因为(根据经验)所讨论的编译器没有额外的簿记,所以它都可以工作。再说一次,我不知道这是否被纳入标准。

    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. 

    }
Run Code Online (Sandbox Code Playgroud)

最后,我会陈述一下观察结果。- 没有扩展参数列表的重载确实有效:

//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); }
Run Code Online (Sandbox Code Playgroud)

摘要:是否有语法允许使用扩展参数列表调用删除的重载,并启用编译器“魔法”。或者,有没有办法通过覆盖将参数添加到新的放置?

可疑的答案:不。

推论:您不能完全自由地偏离 6 个内置新签名。这样做会导致 new 的重载,编译器会生成簿记,但无法访问相应的删除来展开簿记。

警告:您可以偏离内置签名,但只能注入不需要在删除时再次处理的代码(例如插桩)。如果您使用了void* operator new(size_t s)分配版本,则删除仍将正常工作。

(一些事实陈述来自调试器中的实验,可能仅适用于 MSVC8 (cl9)。OP 坐在我旁边的桌子上。)