Using operator new and operator delete with a custom memory pool/allocator
Asked Answered
T

3

6

I'm working on a memory pool/memory allocator implementation and I am setting it up in a manor where only a special "Client" object type can draw from the pool.The client can either be constructed directly onto the pool, or it can use the pool for dynamic memory calls or it could in theory do both. I would like to be able to overload operator new and operator delete in a way that would call my pools "alloc()" and "free()" functions in order to get the memory needed for the object to construct upon.

One of the main issues that I am having is getting my operator delete to be able to free up the memory by calling the pool->free() function I have written. I came up with a hack that fixes it by passing the pool into the constructor and having the destructor do the deallocation work. This is all fine and dandy until someone needs to inherit from this class and override the destructor for their own needs and then forgets to do the memory deallocations. Which is why i want to wrap it all up in the operators so the functionality is tucked away and inherited by default.

My Code Is on GitHub here: https://github.com/zyvitski/Pool

My class definition for the Client is as follows:

class Client
{
public:
    Client();
    Client(Pool* pool);
    ~Client();

    void* operator new(size_t size,Pool* pool);
    void operator delete(void* memory);

    Pool* m_pPool;
};

And the implementation is:

Client::Client()
{

}
Client::Client(Pool* pool)
{
    m_pPool = pool;
}
Client::~Client()
{
    void* p = (void*)this;
    m_pPool->Free(&p);
    m_pPool=nullptr;
}
void* Client::operator new(size_t size, Pool* pool)
{
    if (pool!=nullptr) {
        //use pool allocator
        MemoryBlock** memory=nullptr;
        memory = pool->Alloc(size);
       return *memory;
    }
    else throw new std::bad_alloc;
}
void Client::operator delete(void* memory)
{
    //should somehow free up the memory back to the pool
    // the proper call will be:
    //pool->free(memory);
    //where memory is the address that the pool returned in operator new

}

Here is the example Main() that i'm using for the moment:

int main(int argc, const char * argv[]){
    Pool* pool = new Pool();
    Client* c = new(pool) Client(pool);
    /*
    I'm using a parameter within operator new to pass the pool in for use and i'm also passing the pool as a constructor parameter so i can free up the memory in the destructor
    */

    delete c;
    delete pool;
    return 0;
}

So far my code works, but I want to know if there is a better way to achieve this? Please let me know if anything I am asking/doing is simply impossible, bad practice or just simply dumb. I am on a MacBook Pro right now but i would like to keep my code cross platform if at all possible.

If you have any questions that would help you help me do let me know.

And of course, Thanks in advance to anyone who can help.

Tiatiana answered 6/1, 2014 at 7:55 Comment(0)
F
8

You might store additional information just before the returned memory address

#include <iostream>
#include <type_traits>

class Pool {
public:
    static void* Alloc(std::size_t size) { return data; }
    static void Dealloc(void*) {}
private:
    static char data[1024];
};
char Pool::data[1024];


class Client
{
public:
    void* operator new(size_t size, Pool& pool);
    void operator delete(void* memory);
};


struct MemoryHeader {
    Pool* pool;
};


void* Client::operator new(size_t size, Pool& pool)
{
    auto header = static_cast<MemoryHeader*>(pool.Alloc(sizeof(MemoryHeader) + size));
    std::cout << "    New Header: " << header << '\n';
    header->pool = &pool;
    return header + 1;
}

void Client::operator delete(void* memory)
{
    auto header = static_cast<MemoryHeader*>(memory) - 1;
    std::cout << " Delete Header: " << header << '\n';
    header->pool->Dealloc(header);
}

int main()
{
    Pool pool;
    Client* p = new(pool) Client;
    std::cout << "Client Pointer: " << p << '\n';
    delete p;
    return 0;
}
Fritz answered 6/1, 2014 at 8:46 Comment(2)
So far I'm liking this answer. I see that you edited it about an hour after you posted it? I'm curious as to what and why you changed. I remember there was some byte alignment stuff and you were using a union for something. If you don't mind explaining I'm curious as to what you had going on there and why?Tiatiana
@AlexZywicki that aligned storage was useless (A C++11 thing not required here, i am learning, too)Fritz
T
2

With the help of Dieter Lücking I was able to figure out how to use my pool in operator new and operator delete

Here is the code for operator new:

void* ObjectBase::operator new(size_t size, Pool* pool)
{
    if (pool!=nullptr) {
        //use pool allocation
        MemoryBlock** block = pool->Alloc(size+(sizeof(MemoryHeader)));
        MemoryBlock* t = * block;
        t = (MemoryBlock*)((unsigned char*)t+sizeof(MemoryHeader));
        MemoryHeader* header = new(*block)MemoryHeader(pool);
        header=nullptr;
        return t;
    }
    else{
        //use std allocation
        void* temp = ::operator new(size);
        if (temp!=nullptr) {
            return temp;
        }
        else throw new std::bad_alloc;
    }
}

Here is the code for operator delete

void ObjectBase::operator delete(void* memory)
{
    MemoryBlock* temp = (MemoryBlock*)((unsigned char*)memory-sizeof(MemoryHeader));
    MemoryHeader* header = static_cast<MemoryHeader*>(temp);
    if (header->pool!=nullptr) {
        if (header->pool->Free((MemoryBlock**)&header));
        else
        {
            ::operator delete(memory);
        }
    }
    else{
        ::operator delete(memory);
    }
}

I'm using the "Memory Header" idea that was suggested.

The code is also set up in a way that defaults to using a standard memory allocation call if for some reason the pool fails.

Thanks Again for your help.

Tiatiana answered 8/1, 2014 at 1:15 Comment(0)
D
0

If your delete operator simply calls free, you custom allocator will not do e very good job. The idea of a custom allocator is that it will work with a predefined memory region which it will have control over: when it allocates memory it will be from it's memory region or pool and when the memory is freed, the allocator is 'informed' it can reuse that memory. Now, if you use free, you just return the memory to the heap, not to your memory pool. The way this part is usually done is with the use of smart pointers - to keep track of what memory is available.

Any other mechanism will do as long as you can keep track of which addresses are in use and which are available.

Hope this helps

Digitate answered 6/1, 2014 at 9:19 Comment(1)
Thanks. My pool->free() does not call the regular free() function, it does essentially what you have said. You can take a look if you go pull the git repository I posted a link to. I didn't want to put the code for that in the question because it is rather bulky and not what my question is about directly.Tiatiana

© 2022 - 2024 — McMap. All rights reserved.