How to initialize a variable after a function call, how does a new expression do it?

I am currently working on custom memory allocation, and one of the drawbacks is that I have to write multiple lines to achieve the same result that the new expression provides only one simple call.

Simple initialization:

MyClass *obj = new MyClass(3.14); 

Less simple initialization:

 void *obj_mem = alloc->Allocate(sizeof MyClass, alignof(MyClass)); MyClass *obj = new(obj_mem) MyClass(3.14); 

I'm going to provide my group of projects with allocators like this, and I want them to actually use them, instead of refusing to call new , since we need these faster allocators to manage our memory.

But for this I will have to develop the simplest possible syntax for initializing a variable using my custom allocators.


My decision

It was best to override operator new in each class, as it is a distribution function for the new expression.

 class MyClass { ... void* operator new(size_t size, Allocator *alloc) { return alloc->Allocate(size, alignof(MyClass)); } } 

And then the syntax for initializing the variable becomes what I ultimately want:

 MyClass *obj = new(alloc) MyClass(3.14); 

However, it would be great if I had a common equivalent of the above. Therefore, I would not have to redefine operator new for each class.

+6
source share
4 answers

Kill new completely. You still have to associate creation with destruction.

 template<class T> struct destroy { Alloc* pool = nullptr; void operator()(T* t)const { ASSERT(t); t->~T(); ASSERT(alloc); alloc->Dealloc( t ); } }; template<class T> using my_unique_ptr = std::unique_ptr<T, destroy<T>>; namespace details{ template<class T, class...Args> my_unique_ptr<T> my_make_unique( Alloc* alloc, Args&&...args ) { void* p_data = alloc->Allocate(sizeof(T), alignof(T)); try { T* ret = ::new(p_data) T(std::forward<Args>(args)...); return {ret, destroy<T>{alloc}}; } catch (...) { alloc->Dealloc( p_data ); throw; } } } /// usual one: template<class T, class...Args> my_unique_ptr<T> my_make_unique( Alloc* alloc, Args&&...args ) { return details::my_make_unique<T>( alloc, std::forward<Args>(args)... ); } // permit leading il: template<class T, class U, class...Args> my_unique_ptr<T> my_make_unique( Alloc* alloc, std::initializer_list<U> il, Args&&...args ) { return details::my_make_unique<T>( alloc, il, std::forward<Args>(args)... ); } // for {} based construction: template<class T>struct tag_t{using type=T;}; template<class T>using no_deduction=typename tag_t<T>::type; template<class T> my_unique_ptr<T> my_make_unique( Alloc* alloc, no_deduction<T>&&t ) { return details::my_make_unique<T>( alloc, std::move(t) ); } 

now my_make_unique accepts Alloc* arguments and the construct, and it returns a smart pointer with a set of destruction code.

This unique pointer can be passed to std::shared_ptr<T> implicitly (via move).

+4
source

If you are satisfied with this form of initialization:

 MyClass *obj = new(alloc) MyClass(3.14); 

That is, if it’s good for you to transfer such an object to new , then you can globally overload new with the placement parameters:

 void* operator new(std::size_t size, Allocator *alloc) { return alloc->Allocate(size, alignof(std::max_align_t)); } 

Any use of the new(alloc) array without using the array will call this function. Your big problem is that there is no syntax for the placement syntax. To remove the object correctly, you will need to do what you usually do for any other use of the object created by placing new:

 obj->~Typename(); ::operator delete(obj, sizeof(obj), alloc); 

Of course, this requires an operator delete overload:

 void operator delete(void *obj, std::size_t sz, Allocator *alloc) { alloc->Deallocate(obj, sz, alignof(std::max_align_t)); } 

If you have access to C ++ 17, then operator new and operator delete can take the actual alignment of the object as a parameter.

0
source

You can create a factory template function. Sketch:

 template<class R,class... T> R* create(alloc& alloc,T&&... t) { void *obj_mem = alloc.Allocate(sizeof(R), alignof(R)); R* obj = new(obj_mem) R(std::forward<T>(t)...); return obj: } 
0
source

If you can (and want to) change your allocators, I would add a member function to highlight and build. In class SomeAllocator :

 template <typename T, typename... Args> T* SomeAllocator::Construct(Args&&... args) { auto ptr = Allocate(sizeof(T), alignof(T)); return new (ptr) T(std::forward<Args>(args)...); } 

This will allow you to use it as

 auto obj = alloc->Construct<MyClass>(3.14); 

To destroy and delete, you can - provide a single syntax - add the Destruct method:

 template <typename T> void SomeAllocator::Destruct(T*& t) { t->~T(); Deallocate(t, sizeof(T), alignof(T); t = nullptr; } 
0
source

Source: https://habr.com/ru/post/1012869/


All Articles