【问题标题】:Why use std::make_unique in C++17?为什么在 C++17 中使用 std::make_unique?
【发布时间】:2019-05-21 02:01:10
【问题描述】:

据我了解,C++14 引入了std::make_unique,因为未指定参数评估顺序,这是不安全的:

f(std::unique_ptr<MyClass>(new MyClass(param)), g()); // Syntax A

(说明:如果求值先为原始指针分配内存,然后调用g(),并在std::unique_ptr构造之前抛出异常,则内存泄漏。)

调用std::make_unique 是一种限制调用顺序的方法,从而使事情变得安全:

f(std::make_unique<MyClass>(param), g());             // Syntax B

从那时起,C++17 已经澄清了评估顺序,使得语法 A 也安全,所以这是我的问题:是否还有理由在 C+ 中使用 std::make_unique 而不是 std::unique_ptr 的构造函数+17?你能举一些例子吗?

到目前为止,我能想象的唯一原因是它只允许输入一次MyClass(假设您不需要依赖std::unique_ptr&lt;Base&gt;(new Derived(param)) 的多态性)。然而,这似乎是一个很弱的理由,尤其是当std::make_unique 不允许指定删除器而std::unique_ptr 的构造函数允许指定删除器时。

为了清楚起见,我并不是主张从标准库中删除std::make_unique(至少为了向后兼容保持它是有意义的),而是想知道是否仍然存在强烈的情况首选std::unique_ptr

【问题讨论】:

  • 但是,这似乎是一个很弱的理由 --> 为什么这是一个弱的理由?它有效地减少了代码类型的重复。至于删除器,您在使用std::unique_ptr 时使用自定义删除器的频率如何?这不是反对make_unique的论据
  • 我说这是一个很弱的理由,因为如果一开始就没有 std::make_unique,我认为这不足以将其添加到 STL 中,尤其是当它是一种语法时比使用构造函数更不具有表现力,而不是更多
  • 如果你有一个程序,用 c++14 创建,使用 make_unique,你不希望函数从 stl 中删除。或者,如果您希望它向后兼容。
  • @Serge 这是一个很好的观点,但它有点超出我的问题的对象。我将进行编辑以使其更清晰
  • @Eternal 请停止将 C++ 标准库称为 STL,因为它不正确并会造成混淆。见stackoverflow.com/questions/5205491/…

标签: c++ c++17 unique-ptr


【解决方案1】:

您说得对,主要原因已被删除。仍然有不要使用新的 准则,并且它是减少输入的原因(不必重复输入或使用单词new)。诚然,这些不是强有力的论据,但我真的很喜欢在我的代码中没有看到 new

也不要忘记一致性。你绝对应该使用make_shared,所以使用make_unique 是自然的并且符合模式。然后将 std::make_unique&lt;MyClass&gt;(param) 更改为 std::make_shared&lt;MyClass&gt;(param)(或相反)很简单,语法 A 需要更多的重写。

【讨论】:

  • @reggaeguitar 如果我看到new,我需要停下来想一想:这个指针还能存在多久?我处理得对吗?如果出现异常,是否所有内容都已正确清理?我不想问自己这些问题并浪费我的时间,如果我不使用new,我就不必问这些问题。
  • 假设您对项目的所有源文件执行 grep 操作,但找不到一个 new。这不是很棒吗?
  • “不要使用新”指南的主要优点是它很简单,因此它是一个简单的指南,可以提供给您可能与之合作的经验不足的开发人员。一开始我并没有意识到,但这本身就有价值
  • @NathanOliver 实际上,您绝对不必使用std::make_shared - 想象一下分配对象很大并且有很多std::weak_ptr的情况- s 指向它:最好让对象在最后一个共享指针被销毁后立即被删除,并且只保留一个小的共享区域。
  • @NathanOliver 你不会的。我在说的是std::make_sharedstackoverflow.com/a/20895705/8414561的缺点,其中用于存储对象的内存在最后一个std::weak_ptr消失之前无法释放(即使所有std::shared_ptr-s指向它(以及因此对象本身)已经被销毁)。
【解决方案2】:

make_unique 区分 TT[]T[N]unique_ptr(new ...) 没有。

您可以通过将new[]ed 的指针传递给unique_ptr&lt;T&gt;,或将newed 的指针传递给unique_ptr&lt;T[]&gt;,轻松获得未定义的行为。

【讨论】:

  • 更糟糕的是:它不仅没有,而且完全无法做到。
【解决方案3】:

原因是代码更短,没有重复。比较

f(std::unique_ptr<MyClass>(new MyClass(param)), g());
f(std::make_unique<MyClass>(param), g());

您保存MyClassnew 和大括号。与 ptr 相比,make 只多花费一个字符。

【讨论】:

  • 好吧,正如我在问题中所说,我可以看到它的打字更少,只提到了一次MyClass,但我想知道是否有更强有力的理由使用它
  • 在许多情况下,演绎指南将有助于消除第一个变体中的 &lt;MyClass&gt; 部分。
  • 在 cmets 中已经说过其他答案,但是虽然 c++17 为构造函数引入了模板类型推导,但在 std::unique_ptr 的情况下是不允许的。这与区分std::unique_ptr&lt;T&gt;std::unique_ptr&lt;T[]&gt; 有关系
【解决方案4】:

new 的每次使用都必须经过仔细审核,以确保其终生正确性;它会被删除吗?只有一次?

make_unique 的每次使用都不是为了那些额外的特性;只要拥有对象具有“正确”的生命周期,它就会递归地使唯一指针具有“正确”。

现在,unique_ptr&lt;Foo&gt;(new Foo()) 确实在所有方面1make_unique&lt;Foo&gt;() 相同;它只需要一个更简单的“grep your source code for all uses of new to audit them”。


1 在一般情况下实际上是一个谎言。完美转发并不完美,{},默认init,数组都是例外。

【讨论】:

  • 从技术上讲,unique_ptr&lt;Foo&gt;(new Foo)make_unique&lt;Foo&gt;() 并不相当相同...后者与new Foo() 相同,但除此之外,是的。
  • @barry true,重载 operator new 是可能的。
  • @dedup 那是什么邪恶的 C++17 巫术?
  • @Deduplicator 而 c++17 为构造函数引入了模板类型推导,在 std::unique_ptr 的情况下是不允许的。如果与区分std::unique_ptr&lt;T&gt;std::unique_ptr&lt;T[]&gt; 有关系
  • @Yakk-AdamNevraumont 我不是说重载 new,我只是说 default-init vs value-init。
【解决方案5】:

从那时起,C++17 明确了求值顺序,使得语法 A 也很安全

这真的不够好。依靠最近引入的技术条款作为安全保证并不是一种非常可靠的做法:

  • 有人可能会在 C++14 中编译此代码。
  • 您会鼓励在其他地方使用原始的new,例如通过复制粘贴您的示例。
  • 作为 S.M.建议,由于存在代码重复,因此可能会更改一种类型而不会更改另一种类型。
  • 某种自动 IDE 重构可能会将 new 转移到其他位置(好吧,当然,这种可能性不大)。

一般来说,让您的代码适当/健壮/明确有效 不使用语言分层、在标准中查找次要或晦涩的技术条款是个好主意。

(这与我在here 提出的关于元组销毁顺序的论点基本相同。)

【讨论】:

    【解决方案6】:

    考虑 无效函数(std::unique_ptr(new A()), std::unique_ptr(new B())) { ... }

    假设 new A() 成功,但 new B() 抛出异常:你捕获它以恢复程序的正常执行。不幸的是,C++ 标准并不要求对象 A 被销毁并释放它的内存:内存悄悄地泄漏并且没有办法清理它。通过将 A 和 B 包装到 std::make_uniques 中,您可以确定不会发生泄漏:

    空函数(std::make_unique(), std::make_unique()) { ... } 这里的重点是 std::make_unique 和 std::make_unique 现在是临时对象,并且 C++ 标准中正确指定了临时对象的清理:它们的析构函数将被触发并释放内存。所以如果可以的话,总是更喜欢使用 std::make_unique 和 std::make_shared 分配对象。

    【讨论】:

    • 作者明确指出在 C++17 中不会再发生泄漏。 “从那时起,C++17 明确了评估顺序,使语法 A 也安全,所以这是我的问题:(...)”。你没有回答他的问题。
    猜你喜欢
    • 2019-03-28
    • 1970-01-01
    • 1970-01-01
    • 2020-12-31
    • 2017-03-18
    • 2019-01-15
    • 2020-01-22
    • 1970-01-01
    • 2018-02-11
    相关资源
    最近更新 更多