Сообщение Re: Использование аллокатора от 15.10.2019 4:28
Изменено 15.10.2019 4:56 Erop
Re: Использование аллокатора
Здравствуйте, koenjihyakkei, Вы писали:
K>Всем привет!
K>В некоторых серьезных проектах видел как в классе переопределяют оператор new, для того чтобы выделять память через свой аллокатор.
K>Типа такого:
K>
K>Мне же больше нравится такой вариант:
K>
K>Так вот вопрос, может я чего-то упустил, но я не могу найти плюсов у первого варианта по сравнению со вторым.
K>Вам какой больше нравится, что бы стали использовать в новом проекте?
1) Оба не нравятся, но первый лучше
2) Минусы первого -- не переопределён delete, что опасно. При таком подходе надо как-то следить что бы объекты разрушались на правильных аллокаторах. Например, хранить указатель на аллокатор в том же блоке, что и объект.
То есть, если не отвлекаться на выравнивание, то что-то вроде:
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, что опасно. При таком подходе надо как-то следить что бы объекты разрушались на правильных аллокаторах. Например, хранить указатель на аллокатор в том же блоке, что и объект.
То есть, если не отвлекаться на выравнивание, то что-то вроде:
Но так писать клиентские классы неудобно, так что я бы вынес всё в базу:
и тогда Foo будет
При этом, если do_alloc и do_free сделать виртуальными, то это всё можно будет хитро настраивать, а вызываться это всё будет обычным способом.
Мало того, если в Allocator добавить static или static per thread поле с текущим аллокатором и POII переключалку для него, то можно будет по умолчанию использовать обычный new, а аллокатор передавать через поле
Минусы второго подхода -- не гибкий, хотя и с шаблонами...
P. S.
В реальном коде ещё про new[] и delete[] стоит не забывать
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[] стоит не забывать