Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are new and delete still useful in C++14?

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?

like image 813
Michael Avatar asked Jun 10 '15 17:06

Michael


People also ask

Should I use new and delete in C++?

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.

Should you ever use new in C++?

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.

Is new deprecated in C++?

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 should delete be used in C++?

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.


2 Answers

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:

  • a memory pool
  • an allocator
  • a tagged variant
  • binary messages to a buffer

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.

like image 51
Barry Avatar answered Sep 23 '22 23:09

Barry


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_ptrs 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.

like image 23
Yakk - Adam Nevraumont Avatar answered Sep 19 '22 23:09

Yakk - Adam Nevraumont