Использование аллокатора
От: koenjihyakkei Россия  
Дата: 11.10.19 19:23
Оценка:
Всем привет!

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

Типа такого:
struct Foo {
    Foo(int n) {}
    void* operator new(size_t size, Allocator* allocator) {
        return allocator->Alloc(size);
    }
};

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

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


Мне же больше нравится такой вариант:
struct Foo {
    Foo(int n) {}
};

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

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

Так вот вопрос, может я чего-то упустил, но я не могу найти плюсов у первого варианта по сравнению со вторым.
Вам какой больше нравится, что бы стали использовать в новом проекте?
Re: Использование аллокатора
От: K13 http://akvis.com
Дата: 11.10.19 20:11
Оценка:
Здравствуйте, koenjihyakkei, Вы писали:

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


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


А где во втором варианте нестандартное выделение памяти?
В общем случае в первом варианте вместо malloc может быть что угодно -- например, при написании плагина к какой-нибудь системе "большие" куски памяти надо просить через подсистему хоста. Иначе в 32-битном режиме вылетим по Out Of Memory.

Во втором случае память выделяется стандартным new.
Или надо было уж расписывать malloc + placement new и возврат указателя, чтобы функционал двух вариантов совпадал, отличаясь синтаксисом.

Еще одно соображение: первый вариант позволяет одному экземпляру аллокатора работать с разными типами (разных размеров).
Во втором варианте экземпляр аллокатора будет на каждый класс, даже если у разных классов размеры совпадают.
Отредактировано 11.10.2019 20:13 K13 . Предыдущая версия .
Re: Использование аллокатора
От: andyp  
Дата: 12.10.19 09:04
Оценка:
Здравствуйте, koenjihyakkei, Вы писали:


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


Использование смарт принтеров. Функции типа make_shared внутри себя зовут operator new.
Re[2]: Использование аллокатора
От: night beast СССР  
Дата: 12.10.19 10:35
Оценка:
Здравствуйте, andyp, Вы писали:

A>Использование смарт принтеров. Функции типа make_shared внутри себя зовут operator new.


они зовут обычный new, а не placement (под placement понимается тот, что принимает параметры)
Отредактировано 12.10.2019 11:18 night beast . Предыдущая версия .
Re[3]: Использование аллокатора
От: andyp  
Дата: 12.10.19 11:18
Оценка:
Здравствуйте, night beast, Вы писали:

NB>они зовут обычный new, а не placement


Да, виноват. Невнимательно читал.
Re[2]: Использование аллокатора
От: koenjihyakkei Россия  
Дата: 12.10.19 19:48
Оценка:
Здравствуйте, K13, Вы писали:

K13>А где во втором варианте нестандартное выделение памяти?

K13>В общем случае в первом варианте вместо malloc может быть что угодно -- например, при написании плагина к какой-нибудь системе "большие" куски памяти надо просить через подсистему хоста. Иначе в 32-битном режиме вылетим по Out Of Memory.

Да, там нестандартное выделение памяти, просто тут для упрощения написал malloc.

K13>Во втором случае память выделяется стандартным new.

K13>Или надо было уж расписывать malloc + placement new и возврат указателя, чтобы функционал двух вариантов совпадал, отличаясь синтаксисом.

Да, наверное нужно было написать через malloc + placement new, но это тоже несущественно.

K13>Еще одно соображение: первый вариант позволяет одному экземпляру аллокатора работать с разными типами (разных размеров).

K13>Во втором варианте экземпляр аллокатора будет на каждый класс, даже если у разных классов размеры совпадают.

Вот это уже существенное замечание. Действительно аллокатор используется для разных типов данных.
Но тогда второй вариант можно переписать так:

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

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

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


И тогда мы также можем использовать один аллокатор для аллоцирования разных типов.

Вообще мне кажется это все дело вкуса, но все равно даже с точки зрения вкуса почему первый вариант может быть лучше второго?

Почему собственно я спрашиваю, да просто нужно начинать новый проект, который базируется на готовом проекте, и я хочу ипользовать второй вариант использования аллокаторов, который мне кажется логичнее и проще.
Но прежде хотелось бы быть уверенным что я ничего не упустил и потом не придется все рефакторить.
Re: Использование аллокатора
От: Erop Россия  
Дата: 15.10.19 04:28
Оценка:
Здравствуйте, 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[] стоит не забывать
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Отредактировано 15.10.2019 4:56 Erop . Предыдущая версия .
Re[2]: Использование аллокатора
От: koenjihyakkei Россия  
Дата: 15.10.19 19:38
Оценка:
Здравствуйте, Erop, Вы писали:

E>1) Оба не нравятся, но первый лучше

E>2) Минусы первого -- не переопределён delete, что опасно. При таком подходе надо как-то следить что бы объекты разрушались на правильных аллокаторах. Например, хранить указатель на аллокатор в том же блоке, что и объект.
E>То есть, если не отвлекаться на выравнивание, то что-то вроде:

На самом деле delete переопределен, точнее он просто запрещен, потому что используются только арена аллокаторы.

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


В свое время делал такое

Спасибо за наводку на thread_local, а то как-то мимо меня прошла эта фича.

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


А мне он нравится свой простотой, которую даже сишники запросто поймут и не заметят. А то какой-то new с параметром...
..
Re[3]: Использование аллокатора
От: Erop Россия  
Дата: 17.10.19 19:26
Оценка:
Здравствуйте, koenjihyakkei, Вы писали:

K>А мне он нравится свой простотой, которую даже сишники запросто поймут и не заметят. А то какой-то new с параметром...

Зато явно всё.

Но всё равно на практике через thread_local удобнее обычно...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.