62

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?

dyp
  • 35,820
  • 10
  • 96
  • 156
Michael
  • 5,195
  • 1
  • 28
  • 46
  • 6
    And your question would actually be valid back to C++11 too (earlier if you include Boost) – Cory Kramer Jun 10 '15 at 17:15
  • I don't see how this is a duplicate of that question. You still need to use raw pointers even if you never use `new` and/or `delete`. – Galik Jun 10 '15 at 17:24
  • I would say when you want to call the special operator new as no_throw, placement new or to implement custom allocator (that you want to wrap anyway in a `make_my_unique`). – Jarod42 Jun 10 '15 at 17:27
  • 4
    @CoryKramer: I mentioned C++14 only because C++11 standard only had make_shared, but not make_unique, and that omission makes new useful. – Michael Jun 10 '15 at 17:27
  • 2
    Not worthy of a *separate* answer, so any answer may copy this - I believe `new` is still the idiomatic way to perform [in-place construction](http://stackoverflow.com/questions/222557). – Drew Dormann Jun 10 '15 at 18:07
  • Please correct me if I'm wrong, but smart pointers call the destructor of the pointed objects upon destruction. If that is correct, you still need `new` and `delete` to allocate/free memory. From a user point of view, they are not useful, but from a developer I guess it is the only way to allocate, isn't it? – Javi Jun 11 '15 at 15:14
  • Why make things more complicated for yourself when new and delete are the more sensible options for scientific or algorithmic programming? – FreelanceConsultant Jun 17 '15 at 14:08

4 Answers4

39

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.

Marco A.
  • 41,192
  • 25
  • 117
  • 233
Barry
  • 247,587
  • 26
  • 487
  • 819
  • 10
    You also probably need `new` if you are working with a pre-existing library that implements some kind of GC (Qt, I am looking at you). – sbabbi Jun 10 '15 at 18:17
  • 1
    Interesting. Placement new is a separate beast though. Suppose now that hypothetical C++2x would add place_unique and place_shared, would there still be a need for new/delete? – Michael Jun 10 '15 at 18:25
  • 1
    For in-place construction there's `std::allocator` which allows to cleanly and generically `allocate`/`deallocate` and `construct`/`destroy` elements. – edmz Jun 10 '15 at 18:26
  • The language needs new/delete to manage memory, but new application code should avoid them aside from placement new/delete. Library implementers, however, need memory pointers with the overhead of smart pointers, and new/delete is needed to implement smart pointers – cdmh Jun 11 '15 at 09:10
  • I think it's worth emphasizing that the situation is not symmetric between new and delete, there are many more situations where you will need to use new and placement new, than delete. And, incidentally, putting yourself in a position where you need to use delete is much more error prone than using new. Example: you can write a memory pool where you manage the memory with a unique_ptr, and call placement new for allocation requests. You will never need to use delete. – Nir Friedman Jun 11 '15 at 14:57
  • 1
    @Michael What in the world is `place_shared`? Placement `new` is syntax to directly call a constructor on a block of memory. Non-placement `new` is about first getting space, then constructing. `unique_ptr` and `shared_ptr` are about managing lifetime. `make_unique` and `make_shared` are a combo of getting resources, constructing and managing them in a smart ptr. Placement new, because it does not deal in resources (just construction), is orthogonal to resource management. – Yakk - Adam Nevraumont Jun 11 '15 at 15:06
  • 3
    @NirFriedman Yes: non-custom placement `new` is paired with manually calling the destructor in the same way non-placement `new` is paired with calling `delete`. In a sense, placement `new` is only tangentially related to non-placement `new`: non-placement `new` effectively calls `new`, and `delete` calls the destructor. – Yakk - Adam Nevraumont Jun 11 '15 at 15:07
  • @Yakk Yes, I'm aware of this, thank you for making the relationships clear and detailed. I think that perhaps the answer could be edited so that these points are more clearly highlighted to help people. I think that a) giving them an understanding of the "pairings" you outlined is very good, and b) clarifying that delete is both more error prone and less necessary as a take away, so that developers are appropriately wary of using delete in modern c++ code. – Nir Friedman Jun 11 '15 at 16:07
  • Barry, you can replace calls to `new` for creating custom deleters in a `unique_ptr` and `shared_ptr` by creating a non-custom-deleter `unique_ptr` with `make_unique`, then `.release()`ing it. This may seem like a minor point, but if you directly pass the `.release()` to the constructor of your smart pointer (and the other argument cannot throw), you can reduce the window where you have a non-RAII held resource. – Yakk - Adam Nevraumont Jun 11 '15 at 16:27
  • @Puppy I disagree: `noexcept` is an acceptable window. Which can be non-zero in length. You can pass ownership from one RAII construct to another during a `noexcept` operation perfectly safely, and in general that cannot be avoided (only placed in isolated, safe, checked code) if resources ever move around. And resources whose ownership never moves are boring. – Yakk - Adam Nevraumont Jun 11 '15 at 19:00
  • Inventing tagged variant you can completely avoid using of placement `new`, `reinterpret_cast`, *not* smart pointers and other closer to pure C things. Another words it is not mandatory to use them to create your own discriminated union. – Tomilov Anatoliy Jun 12 '15 at 05:27
7

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.

Yakk - Adam Nevraumont
  • 235,777
  • 25
  • 285
  • 465
  • *"As far as I can tell you cannot both have the "combined allocation of object and reference counting block" and a custom deleter"* I think you can use `allocate_shared` for that purpose. – dyp Jun 11 '15 at 17:15
  • @dyp How do you inject the custom deleter with `allocate_shared`? I know how to store extra data, but I don't know how to avoid the destructor of the `T` from being called when the reference count goes to zero. I guess I could create a buffer with a bool tracking if it has been constructed and a deleter that invokes my custom deletion action, allocate that, use god-mode shared ptr constructor (`shared_ptr( shared_ptr&, T* )` to alias the type of the shared_ptr from a pointer to that to a pointer to `T` while constructing and setting said bool? Gah, and has a bool of overhead! – Yakk - Adam Nevraumont Jun 11 '15 at 17:26
  • Hmm cppreference claims for `allocate_shared` that *"A copy of alloc is stored as part of the control block so that it can be used to deallocate it once both shared and weak reference counts reach zero."* but I can't find such a guarantee in the Standard.. – dyp Jun 11 '15 at 17:29
  • @dyp that deallocation would happen when the weak pointer count goes to zero anyhow, not when the strong pointer count goes to zero. The deleter is called when the strong pointer count reaches zero, and that is what I'm trying to inject into the single-block shared ptr. – Yakk - Adam Nevraumont Jun 11 '15 at 17:31
  • Yes, I was not commenting on the second part of that paragraph (separate deletion of control block and owned object) but on the first part (creation of a `shared_ptr` with a custom deleter via a single allocation). – dyp Jun 11 '15 at 17:34
  • The wording in the Standard looks defective to me: it doesn't require storing the allocator and using it to destruct the object, but at least libc++ does that. Using only the allocation part of the allocator would IMO be kind of silly, too. – dyp Jun 11 '15 at 17:40
  • I still don't see how the allocate shared gives you a custom deleter enabled shared_ptr. Deleter, in the context of a shared_ptr, refers to something very specific? In any case, I think I figured out how to create a single-buffer shared ptr with a custom deleter with arbitrary state, and added it to my answer above. The questionable bit is that I cast the raw buffer ptr to void ptr to T ptr at destruction time: while I think this is valid (as the T was constructed there), the rules in the standard are not something I understand. I would hate to have to store `r`. – Yakk - Adam Nevraumont Jun 11 '15 at 17:52
  • Allocators provide both a `construct` and a `destroy` function. It is essential for certain allocators that `construct` is called instead of placement-new (e.g. `scoped_allocator_adapter`). I don't know of any real use cases for `destruct`, though. I would expect an *allocator-supporting* `shared_ptr` to use the allocator interface for both construction and destruction. But the Standard is very vague and probably defective here. I know that there are issues in implementations wrt `std::vector` and the usage of this interface, as far as I can see there are similar issues here in libc++ – dyp Jun 11 '15 at 17:59
  • *"I would expect an allocator-supporting shared_ptr to use the allocator interface for both construction and destruction."* This is probably where I'm wrong: the ctor of `shared_ptr` takes the deleter and the allocator separately. – dyp Jun 11 '15 at 18:05
4

The only reason I can think of is that occasionally you may wish to use a custom deleter with your unique_ptr or shared_ptr. To use a custom deleter you need to create the smart pointer directly, passing in the result of new. Even this isn't frequent but it does come up in practice.

Other than that it seems that make_shared/make_unique should cover pretty much all uses.

Mark B
  • 91,641
  • 10
  • 102
  • 179
  • 1
    is there an error with the design in my answer, which allows you to create a `shared_ptr` with a custom deleter without calling non-placement `new` directly? For `unique_ptr`, it is even easier: create `std::unique_ptr( make_unique(...).release(), deleter )` -- no call to `new`! (offer invalid if `deleter` constructor throws). – Yakk - Adam Nevraumont Jun 11 '15 at 18:20
1

I would say the only reason for new and delete is to implement other kinds of smart pointers.

For example, the library still does not have intrusive pointers as boost::intrusive_ptr, which is a pity since they are superior for performance reasons to shared pointers, as Andrei Alexandrescu points out.

TheCppZoo
  • 1,131
  • 5
  • 12