12

i want do memory management in my project. i do not want operator global new/delete so i implement a simple memory alloctor. this is my code:

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); }

Then, i can use MYNEW to construct object( also trace alloc info for check memory leak ), and MYDEL to destroy object( erase trace info ).

Everything looks fine... but, when i try to use this method for multiple inheritance class, i found a very serious problem. look my test code below:

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

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

the address of pkB and pkA does not equal. so the memory will not free correct, and the alloc trace info will not erase coorect too...oh...

Is there any way to solve this problem?

Andriy Tylychko
  • 15,967
  • 6
  • 64
  • 112
xfxsworld
  • 283
  • 1
  • 10
  • Is IAllocator virtual base class for all allocable classes? Then just cast away. – Agent_L Dec 04 '12 at 15:04
  • 1
    Just as with ordinary `new` and `delete`, you can only delete the same pointer you got from the allocation. With multiple base classes, they *will* have different addresses because obviously an A and a B cannot live at the same place. – Bo Persson Dec 04 '12 at 15:07
  • no. the IAllocator is the global memory allocator. MYNEW macro use placement new to construct object. – xfxsworld Dec 04 '12 at 15:08
  • As Bo said, if you're new'ing one thing and deleting another, you have a problem. And it's that what you should fix. If you still insist on deleting incorrect pointers, your custom allocator keeps some track of what was allocated and how long, doesn't it? So if the address doesn't match any valid starting block you can tell it's a middle of something - and delete it. OR implement some way for classes A&B to actually return valid pointer to the instance they're part of, via virtual methods for example. And pray noone forgets adding it... It's all fishy, just don't delete incorrect pointers. – Agent_L Dec 04 '12 at 15:15
  • 1
    @BoPersson - Re *you can only delete the same pointer you got from the allocation.* Suppose you allocate an object of some derived class and then cast that to some base class. So long as the base class has a virtual destructor, it's perfectly legal to delete from that base class pointer. With multiple inheritance, that base class pointer may point to a different address than will the derived class pointer. That the base class pointer is different from the as-allocated pointer is not a problem to `delete`. It is a problem to xfxworld's code as written. – David Hammen Dec 04 '12 at 15:39
  • @David Hammen : You know me too well:) – xfxsworld Dec 04 '12 at 15:47
  • same problem with virtual inheritance, e.g. `struct D: virtual B {};` – Andriy Tylychko Apr 04 '17 at 10:34

2 Answers2

8

If ptr points to an instance of a polymorphic class, dynamic_cast<void*>(ptr) will result in a pointer to the most derived object pointed to by ptr. In other words, this dynamic cast yields a pointer to the as-allocated address.

However, using g_Allocator->Dealloc(dynamic_cast<void*>(ptr)) is not a viable solution. The problem is that dynamic_cast<void*>(ptr) is illegal if ptr points to a non-class object (e.g., a primitive) or to an instance of a non-polymorphic class.

What you can do is use SFINAE to create a function that uses this dynamic cast for pointers to polymorphic classes but uses a static cast for pointers to non-class objects and instances of non-polymorphic classes. Boost (and now C++11) provides is_class<T> and is_polymorphic<T> type traits that will help in this regard.

Example:

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);
}
David Hammen
  • 32,454
  • 9
  • 60
  • 108
  • 2
    +1, picked up a new thing today (the `dynamic_cast`, didn't realise it did that...) – Nim Dec 04 '12 at 15:20
  • i did a test just now, and it works fine. i havn't use dynamic_cast before, picked up new thing today~thanks very very very...much.You really powerful:) – xfxsworld Dec 04 '12 at 15:38
  • this works only with polymorphic types. why did you mention `is_class`? how does it help here? – Andriy Tylychko Apr 04 '17 at 10:39
  • @AndyT -- A number of organizations do not allow C++11 or Boost. Those who are stuck in such an organization have to roll their own `is_polymorphic` -- and that means rolling their own `is_class` as well. – David Hammen Apr 04 '17 at 12:43
4

You can try to override operators new and delete for a base class and derive all your classes where you want your custom allocators from that class. Following a simple sample:

#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;
}

One possible output is:

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

In this case operator delete received correct address.

Mircea Ispas
  • 20,260
  • 32
  • 123
  • 211
  • That dynamic cast won't work for a pointer to a non-class object or for a pointer to an instance of a non-polymorphic class. – David Hammen Dec 04 '12 at 15:14
  • I think he meant `Base` as mandatory for custom allocated objects. – Agent_L Dec 04 '12 at 15:17
  • your method is good, but if i do this, I had a lot of places need to modify. and there are many primitive object in my project, I do not want all of them to derive from a base alloc class. Anyway, thanks very much. – xfxsworld Dec 04 '12 at 15:40
  • @xfxsworld Why don't you override global new/delete operators? – Mircea Ispas Dec 04 '12 at 15:48
  • @Felics because my project is a game engine, also thinked as a library. so i think override global new/delete operators is very aggressive, if i do this, anyone use my library are forced to use the operator new/delete, while others maybe do not want to do so. – xfxsworld Dec 04 '12 at 15:55