Использование оператора new и оператора delete с настраиваемым пулом / распределителем памяти

Я работаю над реализацией пула памяти / распределителя памяти и настраиваю его в поместье, где из пула может извлекаться только объект особого типа «Клиент». Клиент может быть либо построен непосредственно в пуле, либо он может используйте пул для вызовов динамической памяти, или теоретически он может делать и то, и другое. Я хотел бы иметь возможность перегрузить operator new и operator delete таким образом, чтобы мои пулы вызывали функции alloc () и free (), чтобы получить память, необходимую для построения объекта.

Одна из основных проблем, с которыми я сталкиваюсь, - это получение моего оператора delete, чтобы он мог освободить память, вызвав функцию pool-> free (), которую я написал. Я придумал хак, который исправляет это, передавая пул в конструктор и заставляя деструктор выполнять работу по освобождению. Это все нормально, пока кому-то не понадобится наследовать от этого класса и переопределить деструктор для своих собственных нужд, а затем забыть об освобождении памяти. Вот почему я хочу обернуть все это операторами, чтобы функциональность была скрыта и унаследована по умолчанию.

Мой код находится на GitHub здесь: https://github.com/zyvitski/Pool

Мое определение класса для клиента выглядит следующим образом:

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

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

    Pool* m_pPool;
};

И реализация:

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

}

Вот пример Main (), который я использую сейчас:

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

Пока мой код работает, но я хочу знать, есть ли лучший способ добиться этого? Пожалуйста, дайте мне знать, если что-то, о чем я прошу / делаю, просто невозможно, это плохая практика или просто глупо. Я использую MacBook Pro прямо сейчас, но я хотел бы, чтобы мой код был кросс-платформенным, если это вообще возможно.

Если у вас есть какие-либо вопросы, которые помогут вам помочь мне, дайте мне знать.

И, конечно же, заранее спасибо всем, кто может помочь.


person Alex Zywicki    schedule 06.01.2014    source источник


Ответы (3)


Вы можете сохранить дополнительную информацию непосредственно перед возвращенным адресом памяти.

#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;
}
person Community    schedule 06.01.2014
comment
Пока мне нравится этот ответ. Я вижу, вы отредактировали это примерно через час после того, как разместили? Мне любопытно, что и почему вы изменили. Я помню, что было кое-что о выравнивании байтов, и вы для чего-то использовали объединение. Если вы не против объяснить, мне любопытно, что у вас там происходит и почему? - person Alex Zywicki; 07.01.2014
comment
@AlexZywicki, что выровненное хранилище было бесполезным (вещь C ++ 11 здесь не требуется, я тоже учусь) - person ; 07.01.2014

С помощью Дитера Люкинга я смог выяснить, как использовать свой пул в operator new < / strong> и оператор delete

Вот код для оператора 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;
    }
}

Вот код для удаления оператора

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

Я использую предложенную идею «Заголовок памяти».

Код также настроен таким образом, что по умолчанию используется стандартный вызов выделения памяти, если по какой-то причине пул выходит из строя.

В очередной раз благодарим за помощь.

person Alex Zywicki    schedule 08.01.2014

Если ваш delete оператор просто вызывает free, ваш собственный распределитель не будет работать очень хорошо. Идея настраиваемого распределителя заключается в том, что он будет работать с предопределенной областью памяти, над которой он будет контролировать: когда он выделяет память, она будет из ее области памяти или пула, а когда память освобождена, распределитель `` информирует '' об этом может повторно использовать эту память. Теперь, если вы используете free, вы просто возвращаете память в кучу, а не в пул памяти. Эта часть обычно выполняется с использованием интеллектуальных указателей - для отслеживания доступной памяти.

Подойдет любой другой механизм, если вы можете отслеживать, какие адреса используются, а какие доступны.

Надеюсь это поможет

person Pandrei    schedule 06.01.2014
comment
Спасибо. Мой pool- ›free () не вызывает обычную функцию free (), он, по сути, делает то, что вы сказали. Вы можете взглянуть, если откроете репозиторий git, на который я разместил ссылку. Я не хотел помещать код для этого в вопрос, потому что он довольно громоздкий и не совсем то, о чем мой вопрос напрямую. - person Alex Zywicki; 06.01.2014