为什么在C ++ 17中使用std :: make_unique?


96

据我了解,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 ++ 17中仍然有理由使用std::make_uniqueover std::unique_ptr的构造函数吗?你能举一些例子吗?

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

只是要清楚一点,我不主张std::make_unique从标准库中删除(至少保持向后兼容才有意义),而是想知道是否仍然存在强烈推荐使用标准库的情况。std::unique_ptr


4
但是,这似乎是一个很薄弱的原因 ->为什么这是一个薄弱的原因?它有效地减少了类型的代码重复。至于删除器,使用时std::unique_ptr多久使用一次自定义删除器?这不是反对的理由make_unique
llllllllll

2
我说这是一个微弱的原因,因为如果一开始就没有std::make_unique,那我就认为没有足够的理由将其添加到STL中,尤其是当它的语法表达能力不及使用构造函数时,更不用说了
永恒

1
如果您有使用make_unique在c ++ 14中创建的程序,则不希望从stl中删除该函数。或者,如果您希望它向后兼容。
Serge

2
@Serge很好,但是除了我的问题对象之外,还有一点。我将进行编辑以使其更清晰
Eternal Eternal

1
@Eternal请停止将C ++标准库称为STL,因为它不正确并会造成混乱。见stackoverflow.com/questions/5205491/...
Marandil

Answers:


73

没错,主要原因已删除。仍然有不要使用新指南,并且打字原因较少(不必重复键入或使用单词new)。诚然,这些并不是强力的论据,但我真的很喜欢new在代码中看不到。

也不要忘记一致性。您绝对应该使用,make_shared因此使用make_unique是自然的并且适合模式。它是那么微不足道的变化std::make_unique<MyClass>(param)std::make_shared<MyClass>(param)(或者相反),其中的语法需要大量重写的多。


40
@reggaeguitar如果看到“ a”,new我需要停下来想一想:该指针将持续多长时间?我处理正确了吗?如果有异常,是否正确清理了所有内容?我不想问自己那些问题,而浪费我的时间,如果我不使用new,我也不必问那些问题。
NathanOliver

5
想象一下,您对项目的所有源文件进行了grep操作,却找不到一个new。这不是很棒吗?
塞巴斯蒂安·马赫

5
“不要使用新的”指南的主要优点是它很简单,因此它是向您可能缺乏经验的开发人员提供的简单指南。我一开始并没有意识到,但是它本身具有价值
永恒

@NathanOliver你其实并不绝对必须使用是std::make_shared-在分配的对象是大,有很多想象的情况下std::weak_ptr-s指向它:它会更好,让对象被删除,只要最后的共享指针被破坏,只具有很小的共享区域。
Dev Null

1
@NathanOliver您不会。我要说的是std::make_shared stackoverflow.com/a/20895705/8414561的缺点,在该内存中,用于存储对象的内存要等到最后一个std::weak_ptr消失后才能释放(即使所有std::shared_ptr-s指向它(和因此对象本身)已被销毁)。
Dev Null

50

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

您可以轻松地通过传递这是一个指针得到了一个未定义的行为new[]编的unique_ptr<T>,或传递,这是一个指针new编的unique_ptr<T[]>


更糟糕的是:它不仅没有,而且完全无法做到。
Deduplicator

21

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

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

您保存MyClassnew和括号。它的成本只有一个性格比较化妆与比较PTR


2
好吧,正如我在问题中说的那样,我可以看到它的打字性很低,只提及了一个MyClass,但是我想知道是否有更充分的理由来使用它
永恒

2
在许多情况下,推导指南将有助于消除<MyClass>第一个变体中的零件。
AnT

9
注释中已经提到了其他答案,但是尽管c ++ 17为构造函数引入了模板类型推导,但在这种情况下是std::unique_ptr不允许的。它与区分std::unique_ptr<T>std::unique_ptr<T[]>
永恒

19

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

的每次使用make_unique都不具有这些额外的特征;只要拥有对象的生存期为“正确”,它就会递归使唯一指针具有“正确”的生存期。

现在,的确unique_ptr<Foo>(new Foo())11的所有方面都相同make_unique<Foo>()。它只需要一个简单的“使用grep的所有源代码new来审核它们”。


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


从技术上讲unique_ptr<Foo>(new Foo)不是到相同的make_unique<Foo>()......后者则new Foo()但是,否则,是的。
巴里

@barry是,新的重载运算符是可能的。
Yakk-Adam Nevraumont

@dedup那是什么犯规的C ++ 17巫术?
Yakk-Adam Nevraumont

2
@Deduplicator,而c ++ 17为构造函数引入了模板类型推导,以防万一std::unique_ptr。如果与区分std::unique_ptr<T>std::unique_ptr<T[]>
永恒有关

@ Yakk-AdamNevraumont我不是说重载new,而是说default-init与value-init。
巴里

0

从那时起,C ++ 17明确了评估顺序,从而使语法A也很安全

这真的不够好。依靠最近引入的技术条款来保证安全不是一个非常可靠的实践:

  • 有人可能会在C ++ 14中编译此代码。
  • 您会鼓励在new其他地方使用raw ,例如通过复制粘贴您的示例。
  • 正如SM所建议的,由于存在代码重复,一种类型可能会更改而另一种类型不会更改。
  • 某种自动IDE重构可能会将其移动到new其他位置(好的,当然,没有太大的机会)。

通常,您的代码适当/健壮/明确有效是一个好主意,而无需诉诸语言安排,查找标准中的次要或晦涩的技术条款。

(这与我在这里对元组销毁的顺序基本相同。)


-1

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

假设new A()成功,但是new B()抛出异常:您捕获到该异常以恢复程序的正常执行。不幸的是,C ++标准不要求销毁对象A并释放其内存:内存静默泄漏,无法清除它。通过将A和B包装到std :: make_uniques中,可以确保不会发生泄漏:

void function(std :: make_unique(),std :: make_unique()){...}这里的要点是std :: make_unique和std :: make_unique现在是临时对象,并且在C ++标准:它们的析构函数将被触发并释放内存。因此,如果可以的话,总是喜欢使用std :: make_unique和std :: make_shared分配对象。


4
作者明确地指出,在C ++ 17中泄漏将不再发生。“从那时起,C ++ 17澄清了评估顺序,也使语法A变得安全,所以这是我的问题:(...)”。您没有回答他的问题。
R2RT
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.