自定义内存分配和释放与多重继承类

12

我希望在我的项目中进行内存管理。我不想使用全局new/delete操作符,因此我实现了一个简单的内存分配器。以下是我的代码:

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来构建对象(同时跟踪分配信息以检查内存泄漏),并使用MYDEL来销毁对象(删除跟踪信息)。

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

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

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

pkB和pkA的地址不相等。因此,内存将无法正确释放,分配跟踪信息也无法正确擦除...哦...

有没有解决这个问题的方法?


IAllocator是所有可分配类的虚基类吗?那么只需要进行转换即可。 - Agent_L
1
就像普通的 newdelete 一样,你只能删除从分配中获得的相同指针。对于多个基类,它们将具有不同的地址,因为显然 A 和 B 不能在同一位置上存在。 - Bo Persson
不是。IAllocator 是全局内存分配器。MYNEW 宏使用放置 new 来构造对象。 - xfxsworld
正如Bo所说,如果你正在新建一个对象并删除另一个对象,那么就会出现问题。你要解决的问题是这个。如果你仍然坚持删除不正确的指针,你的自定义分配器应该会跟踪已分配和使用了多长时间,对吧?所以,如果地址不匹配任何有效的起始块,你可以知道它是某个中间状态,然后将其删除。 或者实现一些方式,使得A和B类通过虚拟方法返回它们所属实例的有效指针。但希望没有人忘记添加它... 总之,这很可疑,千万不要删除不正确的指针。 - Agent_L
1
@BoPersson - Re 你只能删除从分配中得到的相同指针。 假设您分配了某个派生类的对象,然后将其转换为某个基类。只要基类具有虚析构函数,就可以从该基类指针中删除,这是完全合法的。对于多重继承,该基类指针可能指向与派生类指针不同的地址。基类指针与分配的指针不同并不是“删除”的问题。但对于xfxworld的代码而言,这是一个问题。 - David Hammen
显示剩余2条评论
2个回答

8
如果ptr指向一个多态类的实例,则dynamic_cast<void *>(ptr)将导致一个指向ptr所指向的最派生对象的指针。换句话说,这个动态转换产生一个指向分配地址的指针。
然而,使用g_Allocator->Dealloc(dynamic_cast<void *>(ptr))不是可行的解决方案。问题在于,如果ptr指向非类对象(例如原始类型)或非多态类的实例,则dynamic_cast<void *>(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);
}

2
今天又学到了一件新事物(dynamic_cast,没想到它还能这样用...)+1。 - Nim
我刚刚做了一个测试,它运行得很好。我以前从未使用过dynamic_cast<void*>,今天学到了新东西~非常非常非常感谢。你真的很厉害 :) - xfxsworld
这仅适用于多态类型。你为什么提到 is_class<T>?它在这里有什么作用? - Andriy Tylychko
@AndyT -- 许多组织不允许使用 C++11 或 Boost。那些受困于此类组织的人必须自己编写 is_polymorphic,这意味着他们也必须自己编写 is_class - David Hammen

4
您可以尝试覆盖基类的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

在这种情况下,操作符delete接收到了正确的地址。


那个动态转换无法用于指向非类对象的指针或指向非多态类实例的指针。 - David Hammen
我认为他指的是对于自定义分配的对象,Base 是必需的。 - Agent_L
你的方法很好,但如果我这样做,有很多地方需要修改。而且我的项目中有很多原始对象,我不想让它们都从一个基础分配类派生出来。无论如何,非常感谢。 - xfxsworld
@xfxsworld 为什么不重载全局的new/delete运算符呢? - Mircea Ispas
@Felics 因为我的项目是一个游戏引擎,也被认为是一个库。所以我认为重载全局的new/delete操作符是非常激进的,如果我这样做了,任何使用我的库的人都必须使用operator new/delete,而其他人可能不想这样做。 - xfxsworld

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接