Информация об изменениях

Сообщение Re: Использование аллокатора от 15.10.2019 4:28

Изменено 15.10.2019 4:56 Erop

Re: Использование аллокатора
Здравствуйте, koenjihyakkei, Вы писали:

K>Всем привет!


K>В некоторых серьезных проектах видел как в классе переопределяют оператор new, для того чтобы выделять память через свой аллокатор.


K>Типа такого:

K>
K>struct Foo {
K>    Foo(int n) {}
K>    void* operator new(size_t size, Allocator* allocator) {
K>        return allocator->Alloc(size);
K>    }
K>};

K>struct Allocator {
K>    void* Alloc(size_t size) {
K>        return malloc(size);
K>    }
K>};

K>Allocator allocator;
K>Foo* foo = new (&allocator) Foo(42);
K>


K>Мне же больше нравится такой вариант:

K>
K>struct Foo {
K>    Foo(int n) {}
K>};

K>template<class T>
K>struct Allocator {
K>    template<typename Args...>
K>    T* Alloc(Args ... args) {
K>        return new T(std::forward<Args>(args)...);
K>    }
K>};

K>Allocator<Foo> allocator;
K>Foo* foo = allocator.Alloc(42);
K>

K>Так вот вопрос, может я чего-то упустил, но я не могу найти плюсов у первого варианта по сравнению со вторым.
K>Вам какой больше нравится, что бы стали использовать в новом проекте?

1) Оба не нравятся, но первый лучше
2) Минусы первого -- не переопределён delete, что опасно. При таком подходе надо как-то следить что бы объекты разрушались на правильных аллокаторах. Например, хранить указатель на аллокатор в том же блоке, что и объект.
То есть, если не отвлекаться на выравнивание, то что-то вроде:
Re: Использование аллокатора
Здравствуйте, koenjihyakkei, Вы писали:


K>template<class T>

K>struct Allocator {
K> template<typename Args...>
K> T* Alloc(Args ... args) {
K> return new T(std::forward<Args>(args)...);
K> }
K>};

K>Allocator<Foo> allocator;

K>Foo* foo = allocator.Alloc(42);
K>[/ccode]
K>Так вот вопрос, может я чего-то упустил, но я не могу найти плюсов у первого варианта по сравнению со вторым.
K>Вам какой больше нравится, что бы стали использовать в новом проекте?

1) Оба не нравятся, но первый лучше
2) Минусы первого -- не переопределён delete, что опасно. При таком подходе надо как-то следить что бы объекты разрушались на правильных аллокаторах. Например, хранить указатель на аллокатор в том же блоке, что и объект.
То есть, если не отвлекаться на выравнивание, то что-то вроде:
struct Foo {
    Foo(int n) {}
    void* operator new(size_t size, Allocator* allocator=0) {
        return Allocator::Alloc( allocator, size );
    }
    void operator delete( void* obj ) {
        Allocator::Destroy( obj );
    }
    void operator delete( void* obj, Allocator* allocator ) {
        assert( Alloacator::GetAllocator( obj ) == allocator )
        Allocator::destroy( obj );
    }
};

struct Allocator {
    static void* Alloc(Allocator* allocator, size_t size) {
    const block_offset = sizeof( allocator )
    real_size = size + block_offset
    real_block = allocator ? allocator.do_alloc( real_size ) : malloc( real_size )
    *(Allocator**)real_block = allocator
        return block_offset + (char*) real_block;
    }
    static void Destroy( void* obj ) {
    if( obj ) {
         real_block = (char*)(void*)obj)-sizeof(Allocator*)
        if( Allocator* = Allocator::GetAllocator( obj ) ) {
        allocator->do_free( real_block )
        } else {
                free( real_block )
            }
        }
    }
    static Allocator* GetAllocator( void* obj ) {
    if( obj ) {
        return *(Allocator**)( ((char*)(void*)obj)-sizeof(Allocator*) )
         return 0;
    }

    void* do_alloc( size_t real_size ) {
    // тут мог бы быть ваш аллокатор!
        return malloc( real_size )
    void* do_free( void* real_block ) {
    // тут мог бы быть ваш аллокатор!
        free( real_block )
};

K>Allocator allocator;
K>Foo* foo = new (&allocator) Foo(42);
K>


Но так писать клиентские классы неудобно, так что я бы вынес всё в базу:
struct AllocatedOnAllocator {
    void* operator new(size_t size, Allocator* allocator=0) {
        return Allocator::Alloc( allocator, size );
    }
    void operator delete( void* obj ) {
        Allocator::Destroy( obj );
    }
    void operator delete( void* obj, Allocator* allocator ) {
        assert( Alloacator::GetAllocator( obj ) == allocator )
        Allocator::destroy( obj );
    }
};

и тогда Foo будет
struct Foo : AllocatedOnAllocator {
    Foo(int n) {}
};


При этом, если do_alloc и do_free сделать виртуальными, то это всё можно будет хитро настраивать, а вызываться это всё будет обычным способом.

Мало того, если в Allocator добавить static или static per thread поле с текущим аллокатором и POII переключалку для него, то можно будет по умолчанию использовать обычный new, а аллокатор передавать через поле

Минусы второго подхода -- не гибкий, хотя и с шаблонами...

P. S.
В реальном коде ещё про new[] и delete[] стоит не забывать