Given availability of make_unique
and make_shared
, as well as automatic deletion by unique_ptr
and shared_ptr
destructors, what are the situations (apart from supporting legacy code) for using new
and delete
in C++14?
No delete call needed. If you create a pointer to a dynamic object, create clean up code. So when you write new also write delete somehwere at a suitable location (and make sure that is called). For every new keyword there needs to be a delete keyword.
Operator new is an implicit thread synchronizationBoth can be avoided by using placement new/delete and allocating the memory before hand. Or you can allocate/free the memory yourself and then call the constructor/destructor. This is the way std::vector usually works.
The direct answer to your question should be: no, it is not getting deprecated. Even though modern versions of C++ provide many features simplifying memory ownership management (smart pointers), it is still common practice to allocate objects by invoking new OBJ directly.
When delete is used to deallocate memory for a C++ class object, the object's destructor is called before the object's memory is deallocated (if the object has a destructor). If the operand to the delete operator is a modifiable l-value, its value is undefined after the object is deleted.
While smart pointers are preferable to raw pointers in many cases, there are still lots of use-cases for new
/delete
in C++14.
If you need to write anything that requires in-place construction, for example:
you will need to use placement new
and, possibly, delete
. No way around that.
For some containers that you want to write, you may want to use raw pointers for storage.
Even for the standard smart pointers, you will still need new
if you want to use custom deleters since make_unique
and make_shared
don't allow for that.
It is a relatively common choice to use make_unique
and make_shared
rather than raw calls to new
. It is not, however, mandatory. Assuming you choose to follow that convention, there are a few places to use new
.
First, non-custom placement new
(I'll neglect the "non-custom" part, and just call it placement new
) is a completely different card game than standard (non-placement) new
. It is logically paired with manually calling a destructor. Standard new
both acquires a resource from the free store, and constructs an object in it. It is paired with delete
, which destroys the object and recycles the storage to the free store. In a sense, standard new
calls placement new
internally, and standard delete
calls the destructor internally.
Placement new
is the way you directly call a constructor on some storage, and is required for advanced lifetime management code. If you are implementing optional
, a type safe union
on aligned storage, or a smart pointer (with unified storage and non-unified lifetime, like make_shared
), you will be using placement new
. Then at the end of a particular object's lifetime, you directly call its destructor. Like non-placement new
and delete
, placement new
and manual destructor calls come in pairs.
Custom placement new
is another reason to use new
. Custom placement new
can be used to allocate resources from a non-global pool -- scoped allocation, or allocation into a cross-process shared memory page, allocation into video card shared memory, etc -- and other purposes. If you want to write make_unique_from_custom
that allocates its memory using custom placement new, you'd have to use the new
keyword. Custom placement new
could act like placement new (in that it doesn't actually acquire resources, but rather the resource is somehow passed in), or it could act like standard new
(in that it acquires resources, maybe using the arguments passed in).
Custom placement delete
is called if a custom placement new
throws, so you might need to write that. In C++ you don't call custom placement delete
, it (C++) calls you(r overload).
Finally, make_shared
and make_unique
are incomplete functions in that they don't support custom deleters.
If you are writing make_unique_with_deleter
, you can still use make_unique
to allocate the data, and .release()
it into your unique-with-deleter's care. If your deleter wants to stuff its state into the pointed-to buffer instead of into the unique_ptr
or into a separate allocation, you'll need to use placement new
here.
For make_shared
, client code doesn't have access to the "reference counting stub" creation code. As far as I can tell you cannot easily both have the "combined allocation of object and reference counting block" and a custom deleter.
In addition, make_shared
causes the resource allocation (the storage) for the object itself to persist as long as weak_ptr
s to it persist: in some cases this may not be desirable, so you'd want to do a shared_ptr<T>(new T(...))
to avoid that.
In the few cases where you want to call non-placement new
, you can call make_unique
, then .release()
the pointer if you want to manage separately from that unique_ptr
. This increases your RAII coverage of resources, and means that if there are exceptions or other logic errors, you are less likely to leak.
I noted above I didn't know how to use a custom deleter with a shared pointer that uses a single allocation block easily. Here is a sketch of how to do it trickily:
template<class T, class D> struct custom_delete { std::tuple< std::aligned_storage< sizeof(T), alignof(T) >, D, bool > data; bool bCreated() const { return std::get<2>(data); } void markAsCreated() { std::get<2>()=true; } D&& d()&& { return std::get<1>(std::move(data)); } void* buff() { return &std::get<0>(data); } T* t() { return static_cast<T*>(static_cast<void*>(buff())); } template<class...Ts> explicit custom_delete(Ts...&&ts):data( {},D(std::forward<Ts>(ts)...),false ){} custom_delete(custom_delete&&)=default; ~custom_delete() { if (bCreated()) std::move(*this).d()(t()); } }; template<class T, class D, class...Ts, class dD=std::decay_t<D>> std::shared_ptr<T> make_shared_with_deleter( D&& d, Ts&&... ts ) { auto internal = std::make_shared<custom_delete<T, dD>>(std::forward<D>(d)); if (!internal) return {}; T* r = new(internal->data.buff()) T(std::forward<Ts>(ts...)); internal->markAsCreated(); return { internal, r }; }
I think that should do it. I made an attempt to allow stateless deleters to use no up space by using a tuple
, but I may have screwed up.
In a library-quality solution, if T::T(Ts...)
is noexcept
, I could remove the bCreated
overhead, as there would be no opportunity for a custom_delete
to have to be destroyed before the T
is constructed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With