问题 自定义内存alloc和dealloc多个继承类


我想在我的项目中进行内存管理。我不希望operator global new / delete所以我实现了一个简单的内存alloctor。这是我的代码:

class IAllocator
{
public:
    void* Alloc( unsigned int size )
    {
        1. alloc memory.
        2. trace alloc.
    }
    void Dealloc( void* ptr )
    {
        1. free memory.
        2. erase trace info.
    }
    template< typename T >
    void Destructor( T* ptr )
    {
        if ( ptr )
            ptr->~T();
    }
};
// macro for use easy.
# define MYNEW( T ) new ( g_Allocator->Alloc( sizeof( T ) ) ) T
# define MYDEL( ptr ) if (ptr) { g_Allocator->Destructor(ptr); g_Allocator->Dealloc(ptr); }

然后,我可以使用MYNEW构建对象(还有用于检查内存泄漏的跟踪alloc信息),以及用于销毁对象的MYDEL(擦除跟踪信息)。

一切看起来很好......但是,当我尝试将此方法用于多继承类时,我发现了一个非常严重的问题。看下面我的测试代码:

class A { ... };
class B { ... };
class C : public A, public B { ... };

C* pkC = MYNEW( C );
B* pkB = (B*)pkA;
MYDEL( pkB );

pkB和pkA的地址不相等。所以内存不会自由纠正,而且分配跟踪信息也不会擦除coorect ...哦......

有什么方法可以解决这个问题吗?


8134
2017-12-04 14:43


起源

所有可分配类的IAllocator虚拟基类是什么?然后就扔掉了。 - Agent_L
就像平常一样 new 和 delete,您只能删除从分配中获得的相同指针。有多个基类,他们 将 有不同的地址,因为很明显A和B不能住在同一个地方。 - Bo Persson
没有。 IAllocator是全局内存分配器。 MYNEW宏使用placement new来构造对象。 - xfxsworld
正如薄熙来所说,如果你是新事物并删除另一件事,你就会遇到问题。这就是你应该解决的问题。如果你仍然坚持删除不正确的指针,你的自定义分配器会保留一些分配的内容以及多长时间,不是吗?因此,如果地址与任何有效的起始块不匹配,您可以告诉它是中间的东西 - 并删除它。或者为类A和B实现某种方式,例如通过虚拟方法实际返回指向它们所属实例的有效指针。并祈祷没有人忘记添加它...这都是可疑的,只是不要删除不正确的指针。 - Agent_L
@BoPersson - Re 您只能删除从分配中获得的相同指针。 假设您分配了某个派生类的对象,然后将其转换为某个基类。只要基类具有虚拟析构函数,从该基类指针中删除就完全合法。使用多重继承时,该基类指针可能指向与派生类指针不同的地址。基类指针与as分配的指针不同,这不是问题 delete。这是xfxworld编写的代码的问题。 - David Hammen


答案:


如果 ptr 指向多态类的实例, dynamic_cast<void*>(ptr) 将导致指向指向的最派生对象的指针 ptr。换句话说,此动态转换产生指向分配地址的指针。

但是,使用 g_Allocator->Dealloc(dynamic_cast<void*>(ptr)) 不是一个可行的解决方案。问题是 dynamic_cast<void*>(ptr) 如果是非法的 ptr 指向非类对象(例如,基元)或非多态类的实例。

你可以做的是使用SFINAE来创建一个函数,该函数使用这个动态转换来指向多态类,但是使用静态转换来指向非类对象和非多态类的实例。 Boost(现在是C ++ 11)提供了 is_class<T> 和 is_polymorphic<T> 类型特征将有助于这方面。

例:

template <typename T, bool is_poly>
struct GetAllocatedPointerHelper {
   static void* cast (T* ptr) { return ptr; }
};

template <typename T>
struct GetAllocatedPointerHelper<T, true> {
   static void* cast (T* ptr) { return dynamic_cast<void*>(ptr); }
};

template<typename T>
inline void*
get_allocated_pointer (T* ptr)
{
   const bool is_poly = Boost::is_polymorphic<T>::value;
   return GetAllocatedPointerHelper<T, is_poly>::cast(ptr);
}

7
2017-12-04 15:13



+1,今天拿起一件新东西( dynamic_cast,没意识到它做到了......) - Nim
我刚刚做了一个测试,它运行正常。我之前没有使用dynamic_cast <void *>,今天拿起新东西〜非常非常非常感谢...你真的很强大:) - xfxsworld
这仅适用于多态类型。你为什么提到 is_class<T>?它在这里有什么用? - Andriy Tylychko
@AndyT - 许多组织不允许使用C ++ 11或Boost。那些被困在这样一个组织中的人必须自己动手 is_polymorphic  - 这意味着自己滚动 is_class 同样。 - David Hammen


您可以尝试覆盖基类的运算符new和delete,并从您希望自定义分配器的类中派生所有类。以下简单示例:

#include <cstdio>
#include <cstdlib>

class Base
{
    public:
        virtual ~Base(){};

        void* operator new(size_t size){return malloc(size);}
        void operator delete(void* pointer){printf("\n%x\n", pointer); free(pointer);}
};

class A : public virtual Base
{
    public:
        ~A(){printf("~A");};
};

class B : public virtual Base
{
    public:
        ~B(){printf("~B");};
};

class C : public A, public B
{
    public:
        ~C(){printf("~C");};
};

int main()
{
    C* c = new C();
    printf("%x\n", c);

    B* b = dynamic_cast<B*>(c);
    printf("%x\n", b);

    delete b;

    return 0;
}

一个可能的输出是:

5831d0 5831d4~C~B~A 5831d0

在这种情况下,operator delete收到了正确的地址。


4
2017-12-04 15:07



该动态强制转换不适用于指向非类对象的指针或指向非多态类的实例的指针。 - David Hammen
我想他的意思是 Base 作为自定义分配对象的必需项。 - Agent_L
你的方法很好,但如果我这样做,我有很多地方需要修改。在我的项目中有许多原始对象,我不希望它们都来自基本的alloc类。无论如何,非常感谢。 - xfxsworld
@xfxsworld为什么不覆盖全局新/删除运算符? - Felics
@Felics因为我的项目是一个游戏引擎,也被认为是一个库。所以我认为覆盖全局新/删除运算符是非常积极的,如果我这样做,任何人使用我的库都被迫使用运算符new / delete,而其他人可能不想这样做。 - xfxsworld