{"id":107300,"date":"2022-10-19T07:00:00","date_gmt":"2022-10-19T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=107300"},"modified":"2022-10-19T07:35:39","modified_gmt":"2022-10-19T14:35:39","slug":"20221019-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20221019-00\/?p=107300","title":{"rendered":"Why is there a <CODE>make_unique<\/CODE>? Why not just overload the <CODE>unique_ptr<\/CODE> constructor?"},"content":{"rendered":"<p>At first, there was no <code>make_unique<\/code>. Only <code>unique_ptr<\/code>. And for expository simplicity, let&#8217;s focus just on the non-array version of <code>unique_ptr<\/code>.<\/p>\n<p>There&#8217;s <a href=\"http:\/\/www.open-std.org\/jtc1\/sc22\/wg21\/docs\/papers\/2013\/n3588.txt\"> the proposal for <code>make_unique<\/code><\/a>, written by our pal <a href=\"https:\/\/twitter.com\/StephanTLavavej\"> Stephan T. Lavavej<\/a>. It cites a few motivating issues for the <code>make_unique<\/code> function:<\/p>\n<ol>\n<li>Parallel construction with <code>make_shared<\/code>.<\/li>\n<li>Avoiding the need to use the <code>new<\/code> operator explicitly, thereby permitting the simple rule: &#8220;Don&#8217;t write <code>new<\/code>.&#8221; Prior to <code>make_unique<\/code>, the rule was &#8220;Don&#8217;t write <code>new<\/code>, except to construct a <code>unique_ptr<\/code>.&#8221;<\/li>\n<li>Avoiding having to say the type name twice: <code>std::unique_ptr&lt;T&gt;(new T(args))<\/code>.<\/li>\n<li>Avoid a memory leak due to unspecified order of evaluation if a <code>std::unique_ptr<\/code> is constructed from a newly <code>new<\/code>&#8216;d pointer as part of a larger expression which could throw. <a href=\"https:\/\/www.cppstories.com\/2021\/evaluation-order-cpp17\/\"> More details here<\/a>.<\/li>\n<\/ol>\n<p>But couldn&#8217;t we have solved this problem by adding a new constructor to <code>unique_ptr<\/code>?<\/p>\n<pre>template&lt;typename T&gt;\r\nstruct unique_ptr\r\n{\r\n    ...\r\n\r\n    template&lt;typename... Args&gt;\r\n    unique_ptr(Args&amp;&amp;... args) :\r\n        unique_ptr(new T(std::forward&lt;Args&gt;(args)...)) {}\r\n};\r\n<\/pre>\n<p>With this new overload, you can write<\/p>\n<pre>\/\/ was p = std::make_unique&lt;Thing&gt;(arg1, arg2, arg3);\r\nauto p = std::unique_ptr&lt;Thing&gt;(arg1, arg2, arg3);\r\n<\/pre>\n<p>This seems convenient (avoids introducing a new name), but it still has problems. For example, consider this:<\/p>\n<pre>struct Node\r\n{\r\n    Node(Node* parent = nullptr);\r\n};\r\n\r\nauto create_child(Node* parent)\r\n{\r\n    \/\/ was return std::make_unique&lt;Node&gt;(parent);\r\n    return std::unique_ptr&lt;Node&gt;(parent);\r\n}\r\n<\/pre>\n<p>This version looks like it&#8217;s create a new child node with the specified parent, but since the constructor parameter is a pointer to the same type, what this really does is create a <code>unique_ptr<\/code> that manages the parent pointer. Everything will compile, and it may even run for a while, inadvertently updating the wrong node, and eventually leading to a double-free bug.<\/p>\n<p>And then there&#8217;s the converse problem:<\/p>\n<pre>struct NodeSource\r\n{\r\n    operator Node*();\r\n};\r\n\r\nauto wrap_proxy(NodeSource const&amp; source)\r\n{\r\n    \/\/ was return std::make_unique&lt;Node&gt;(source);\r\n    return std::unique_ptr&lt;Node&gt;(source);\r\n}\r\n<\/pre>\n<p>This time, we want to create a <code>unique_ptr<\/code> that manages the object produced by the <code>Node\u00adSource<\/code>&#8216;s conversion operator. A common case where you encounter this is if the <code>Node\u00adSource<\/code> is some sort of proxy object. But since the parameter is not literally a <code>Node*<\/code>, this gets picked up by the new overload and is interpreted as<\/p>\n<pre>    return std::unique_ptr&lt;Node&gt;(new Node(source));\r\n<\/pre>\n<p>For backward compatibility, both of these cases must resolve to the constructor that takes a raw pointer to a <code>Node<\/code>. That can probably be accomplished via a special overload that takes exactly one universal reference, and a little SFINAE, but it&#8217;s starting to get complicated.<\/p>\n<p><i>The default constructor has entered the chat<\/i>:<\/p>\n<pre>auto make_something()\r\n{\r\n    \/\/ was return std::make_unique&lt;Node&gt;();\r\n    return std::unique_ptr&lt;Node&gt;();\r\n}\r\n<\/pre>\n<p>Does this create an empty <code>unique_ptr<\/code>? Or does it create a new default-constructed <code>Node<\/code> and then create a <code>unique_ptr<\/code> that manages it?<\/p>\n<p>For backward compatibility, this must create an empty <code>unique_ptr<\/code>, so now you have a third special case where passing <code>Node<\/code> constructor parameters to <code>unique_ptr<\/code> doesn&#8217;t actually construct a <code>Node<\/code>.<\/p>\n<p><i>The move and copy constructors have entered the chat<\/i>:<\/p>\n<pre>struct ListNode\r\n{\r\n    ListNode(std::unique_ptr&lt;ListNode&gt; rest);\r\n};\r\n\r\nauto prepend_node(std::unique_ptr&lt;ListNode&gt; rest)\r\n{\r\n    \/\/ was return std::unique_ptr&lt;ListNode&gt;(\r\n    \/\/    new ListNode(std::move(rest));\r\n    return std::unique_ptr&lt;ListNode&gt;(std::move(rest));\r\n}\r\n<\/pre>\n<p>Does this create a new <code>ListNode<\/code> object, using <code>rest<\/code> as the constructor parameter? Or does this move-construct an existing <code>std::unique_ptr<\/code>? Again, for backward compatibility, this must move-construct the <code>std::unique_ptr<\/code>.<\/p>\n<p>Okay, so if you do some SFINAE magic and carve out the special cases for backward compatibility, you&#8217;ve resolved the <i>technical<\/i> ambiguity. But you&#8217;ve done nothing to address the <i>semantic<\/i> ambiguity.<\/p>\n<pre>contoso::table&lt;Node*&gt; nodes;\r\n...\r\nauto p = std::unique_ptr&lt;Node&gt;(nodes.get(i));\r\n<\/pre>\n<p>Does this get a <code>Node*<\/code> from the table and transfer ownership of it to a <code>unique_ptr<\/code>? Or does this get a <code>Node*<\/code> from the table and create a new <code>Node<\/code> from it?<\/p>\n<p>As we noted earlier, compatibility requires that we interpret this as an ownership transfer, and if you want to create a new node, you have to do so explicitly:<\/p>\n<pre>auto p = std::unique_ptr&lt;Node&gt;(new Node(nodes.get(i));\r\n<\/pre>\n<p>What makes this even more confusing is that similar expressions represent the creation of a new <code>Node<\/code> without having to write out the <code>new<\/code>:<\/p>\n<pre>\/\/ new Node(Node*, bool)\r\nauto p = std::unique_ptr&lt;Node&gt;(nodes.get(i), true);\r\n\r\n\/\/ new Node(42)\r\nauto p = std::unique_ptr&lt;Node&gt;(42);\r\n\r\n\/\/ does not create a new Node (!)\r\nauto p = std::unique_ptr&lt;Node&gt;(nodes.get(i));\r\n<\/pre>\n<p>In addition to the confusion over whether this is an ownership transfer or a creation, it is unforgiving of typos like<\/p>\n<pre>Node* n;\r\n\r\n\/\/ This takes ownership of n\r\nauto p = std::unique_ptr&lt;Node&gt;(n);\r\n\r\n\/\/ This creates a new Node that is a copy of *n\r\nauto p = std::unique_ptr&lt;Node&gt;(*n);\r\n<\/pre>\n<p>To avoid this pit of failure, we probably should use a tag type to indicate whether we are taking ownership or making a new object.<\/p>\n<pre>template&lt;typename T&gt;\r\nstruct unique_ptr\r\n{\r\n    ...\r\n\r\n    template&lt;typename... Args&gt;\r\n    unique_ptr(in_place_t, Args&amp;&amp;... args) :\r\n        unique_ptr(new T(std::forward&lt;Args&gt;(args)...)) {}\r\n};\r\n\r\nNode* n;\r\n\r\n\/\/ Take ownership of n\r\nauto p = std::unique_ptr&lt;Node&gt;(n);\r\n\r\n\/\/ Create a new Node with n as its parent\r\nauto p = std::unique_ptr&lt;Node&gt;(std::in_place, n);\r\n\r\n\/\/ Create an empty unique_ptr\r\nauto p = std::unique_ptr&lt;Node&gt;();\r\n\r\n\/\/ Create a new default Node and wrap it in a unique_ptr\r\nauto p = std::unique_ptr&lt;Node&gt;(std::in_place);\r\n\r\n\/\/ Move-construct a new unique_ptr from an existing one\r\nstd::unique_ptr&lt;ListNode&gt; rest = \/* ... *\/;\r\nauto q = std::unique_ptr&lt;ListNode&gt;(std::move(rest));\r\n\r\n\/\/ Move-construct a new unique_ptr from an existing one\r\nauto q = std::unique_ptr&lt;ListNode&gt;(std::in_place, std::move(rest));\r\n<\/pre>\n<p>At this point, the new overload seems much more hassle than it&#8217;s worth. You may as well just factor the &#8220;make a new Node&#8221; feature into a separate function <code>make_unique<\/code>. This is more explicit that it makes a new Node, and it&#8217;s less typing anyway.<\/p>\n<pre>\/\/ Take ownership of n\r\nstd::unique_ptr&lt;Node&gt; p(n);\r\n\r\n\/\/ Create a new Node with n as its parent\r\nauto p = std::make_unique&lt;Node&gt;(n);\r\n\r\n\/\/ Create an empty unique_ptr\r\nauto p = std::unique_ptr&lt;Node&gt;();\r\n\r\n\/\/ Create a new default Node and wrap it in a unique_ptr\r\nauto p = std::make_unique&lt;Node&gt;();\r\n\r\n\/\/ Move-construct a new unique_ptr from an existing one\r\nstd::unique_ptr&lt;ListNode&gt; rest = \/* ... *\/;\r\nauto q = std::unique_ptr&lt;ListNode&gt;(std::move(rest));\r\n\r\n\/\/ Move-construct a new unique_ptr from an existing one\r\nauto q = std::make_unique&lt;ListNode&gt;(std::move(rest));\r\n<\/pre>\n<p>If you want to make a new object, use the <code>make_unique<\/code> function.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You&#8217;ll have to resolve the ambiguity, so you&#8217;re still typing a lot.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-107300","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>You&#8217;ll have to resolve the ambiguity, so you&#8217;re still typing a lot.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107300","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=107300"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107300\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=107300"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=107300"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=107300"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}