0

I don't ask about new operator like here. Please, read question carefully.

I want to know why we need special function make_unique over special constructor of unique_ptr.

unique_ptr could use constructor like this to make make_unique unneccessary:

template<typename T, typename ...TArgs>
unique_ptr::unique_ptr(TArgs&&... args)
        : inner_ptr(new T(std::forward(args)...))
{}
  • 2
    IIRC it was originaly added to pair well with [`std::make_shared`](https://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared). – Some programmer dude Oct 16 '21 at 12:24
  • 1
    It allows T to be deduced: `std::make_unique(some_function())` is shorter than `std::unique_ptr(some_function())`. This predated CTAD. – Raymond Chen Oct 16 '21 at 13:59
  • @RaymondChen - `T` must be specified explicitly in every overload of `make_unique` – StoryTeller - Unslander Monica Oct 16 '21 at 14:01
  • 1
    @UnslanderMonica You're right. Yet I remember doing it. Maybe it was just a dream. Here's [the original proposal](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3588.txt) for rationale behind `make_unique`. Also, your proposed `unique_ptr` constructor would be ambiguous: Consider `struct S { S(S* parent = nullptr); }; p1 = new S(); p2 = std::unique_ptr(p1);`. Is this creating a `unique_ptr` to manage the existing `p1`? Or is this creating a `unique_ptr` to create a new `S` which has the `p1` as its parent? – Raymond Chen Oct 16 '21 at 14:23
  • 1
    @RaymondChen - I'm not the person who asked this question. Was just pondering upon an answer when your comment popped up so I responded. I agree with your point about the ambiguity. – StoryTeller - Unslander Monica Oct 16 '21 at 14:27
  • @RaymondChen template parameter T must be proposed in make_unique too, TArgs parameter can be deduced as it does in make_unique. – Angelicos Phosphoros Oct 16 '21 at 15:34
  • @RaymondChen In for your example, there would run overload resolution rules which would choose more suitable constructor of unique_ptr (the one which takes pointer into ownership). – Angelicos Phosphoros Oct 16 '21 at 15:37
  • 2
    @AngelicosPhosphoros - You entirely missed Raymond's point. It isn't about the *technicality* of an ambiguity, it's about the declaration being unclear to anyone reading it since the semantics of the unique pointer and the S are in conflict. Furthermore, it's not impossible to construct examples that **would be** ambiguous to the compiler too. And SFINAEing the whole thing would be a nightmare to an implementer. – StoryTeller - Unslander Monica Oct 16 '21 at 15:48
  • @StoryTeller-UnslanderMonica Thanks for the explanation. This is more clear for me now. – Angelicos Phosphoros Oct 16 '21 at 16:55
  • Well, I sometimes wonder, why mods don't ever try to read disclaimer written especially for them. – Angelicos Phosphoros Oct 16 '21 at 17:02

2 Answers2

6

There's a couple of reasons for this.

The first has to do with a bit of C++ history. Prior to C++17, there was no exception safety with the constructor, so doing the following:

some_func(std::unique_ptr<T1>(), std::unique_ptr<T2>())

would leak memory were the constructor for T1 to throw an exception, as the C++ standard did not require that, in this case the first unique_ptr, should have it's memory deallocated. This is no longer the case since C++17

Second, it allows for a more general rule of "never to use new" which without make_unique would be "never use new except for when using unique_ptr or shared_ptr"

Also there's the added bonus of no redundant typing, as with the constructor you have to do:

auto p = std::unique_ptr<T>(new T());

listing T twice, which can be particularly ugly in the case of long type names. With make_unique this shortens to

auto p = std::make_unique<T>();
msimonelli
  • 393
  • 1
  • 6
  • Thanks. The latter can be achieved by my constructor too as well. `auto p = std::unique_ptr(my_type_arg1, my_type_arg2);` – Angelicos Phosphoros Oct 16 '21 at 13:16
  • >Prior to C++17, there was no exception safety with the constructor, so doing the following: Where can I read more about this? – Angelicos Phosphoros Oct 16 '21 at 13:18
  • 3
    *"Prior to C++17, there was no exception safety with the constructor"* - Your example doesn't apply to the pre-C++17 problem. Things like `std::unique_ptr(new T1), std::unique_ptr(new T2)` were unsafe, but this is not what the OP is suggesting. The `new` happens **inside the c'tor** of `unique_ptr`, as part of its execution. And just like regular function calls, there can be no interleaving. – StoryTeller - Unslander Monica Oct 16 '21 at 13:41
  • Put another way, if the OP's suggestion is unsafe, then so is `make_unique`! – StoryTeller - Unslander Monica Oct 16 '21 at 13:42
  • @AngelicosPhosphoros see [this](https://herbsutter.com/gotw/_102/) – msimonelli Oct 16 '21 at 14:07
  • @msimonelli I posted in my answer that my constructor already solves this problem because allocation happens inside of my proposed constructor. – Angelicos Phosphoros Oct 16 '21 at 15:33
2

I want to summarize discussion with Some programmer dude, StoryTeller - Unslander Monica and Raymond Chen

So, there are 2 reasons:

  1. std::make_unique pairs well with std::make_shared which was introduced earlier so this was easier to learn than new constructor for unique_ptr.
  2. There is possible ambiguity between constructor of unique_ptr and constructor of inner value type (T) if this type have own constructor which takes a pointer to self type. E.g.
struct Inner{
  Inner() = default;
  Inner(Inner* parent_node): parent(parent_node){}
  
  Inner* parent = nullptr;
};


Inner* parent = make_parent();
// It would be not clear for human which constructor must be called here
std::unique_ptr<Inner> child(parent);

Compiler can deduce which constructor should be called here but it is hard for human. So having function std::make_unique is beneficial because it is clear that all constructors of std::unique_ptr create only unique_ptr and never call inner value constructor while std::make_unique would always call constructor of inner value. This makes code much easier to reason about.

Thanks to everyone for discussion!