make_unique和完美的转发


216

为什么std::make_unique标准C ++ 11库中没有功能模板?我发现

std::unique_ptr<SomeUserDefinedType> p(new SomeUserDefinedType(1, 2, 3));

有点冗长。以下内容会更好吗?

auto p = std::make_unique<SomeUserDefinedType>(1, 2, 3);

new很好地隐藏了,只提及了一次类型。

无论如何,这是我尝试实现的make_unique

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

我花了很std::forward长时间才可以编译这些东西,但是我不确定它是否正确。是吗?到底是什么std::forward<Args>(args)...意思?编译器如何处理?


1
可以肯定的是,我们之前已经进行过讨论...也请注意,它unique_ptr采用了您应该以某种方式允许的第二个模板参数-与有所不同shared_ptr
Kerrek SB 2011年

5
@Kerrek:我认为make_unique使用自定义删除器进行参数化没有任何意义,因为显然它是通过Plain old进行分配的new,因此必须使用Plain Old delete:)
fredoverflow 2011年

1
@Fred:是的。因此,建议的范围make_unique仅限于new分配...好吧,如果您想编写它就可以了,但是我可以看到为什么这样的东西不属于标准的一部分。
Kerrek SB 2011年

4
实际上,make_unique由于的构造函数std::unique_ptr是显式的,因此我喜欢使用模板,因此unique_ptr从函数返回很冗长。另外,我宁愿使用而auto p = make_unique<foo>(bar, baz)不是std::unique_ptr<foo> p(new foo(bar, baz))
Alexandre C.

5
make_unique即将到来C++14,请参见isocpp.org/blog/2013/04/trip-report-iso-c-spring-2013-meeting
stefan

Answers:


157

C ++标准化委员会主席Herb Sutter在他的博客中写道:

C ++ 11不包括make_unique的部分内容是疏忽,几乎可以肯定的是将来会添加它。

他还给出了与OP给出的实现相同的实现。

编辑: std::make_unique现在是C ++ 14的一部分。


2
一个make_unique函数模板本身并不保证异常安全的电话。它依赖于约定,调用者使用它。相反,严格的静态类型检查(这是C ++和C之间的主要区别)是基于通过类型强制执行安全性的思想而建立的。为此,make_unique可以简单地成为类而不是函数。例如,请参阅我在2010年5月发布的博客文章。该文章也链接到Herb博客上的讨论中。
干杯和健康。-Alf 2012年

1
@DavidRodríguez-dribeas:阅读Herb的博客以了解异常安全性问题。忘掉“不是设计成派生的”,那只是垃圾。而不是我建议创建make_unique一个类,我现在认为最好是将它生成一个产生a的函数,make_unique_t原因是最不正确的解析是一个问题:-)。
干杯和健康。-Alf 2012年

1
@ Cheersandhth.-Alf:也许我们读了另一篇文章,因为我刚刚读过的那篇文章清楚地表明了它make_unique提供了强大的异常保证。或者,也许您将工具与它的使用混在一起,在这种情况下,没有任何功能是例外安全的。考虑到void f( int *, int* ){},显然可以提供no throw保证,但是根据您的判断,它不是异常安全的,因为它可能被滥用。更糟糕的void f( int, int ) {}是,也不是异常安全!:typedef unique_ptr<int> up; f( *up(new int(5)), *up(new int(10)))...
大卫·罗德里格斯(DavidRodríguez)-dribeas 2012年

2
@ Cheersandhth.-阿尔夫:我问了一下异常问题, make_unique实现如上,你点我的萨特,文章认为我的谷歌福指着我的状态了一篇文章,make_unique提供了强大的异常保证,这与上面的说法。如果您有其他文章,我兴趣阅读。因此,我最初的问题make_unique(如上所述)如何不安全地例外?(旁注:是的,我认为make_unique 改善的地方异常安全的地方可以应用)
大卫·罗德里格斯- dribeas

3
...我也不追求安全性较低的make_unique功能。首先,我没有看到这个不安全,也没有看到添加额外的类型如何使其更安全。我所知道的是,我有兴趣了解此实现可能遇到的问题(我看不到任何问题)以及替代实现如何解决这些问题。什么是惯例make_unique取决于?您将如何使用类型检查来加强安全性?这些是我希望回答的两个问题。
大卫·罗德里格斯(DavidRodríguez)-dribeas 2012年

78

不错,但是Stephan T. Lavavej(更名为STL)为提供了更好的解决方案make_unique,该解决方案在数组版本中可以正常使用。

#include <memory>
#include <type_traits>
#include <utility>

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::false_type, Args&&... args) {
  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::true_type, Args&&... args) {
   static_assert(std::extent<T>::value == 0,
       "make_unique<T[N]>() is forbidden, please use make_unique<T[]>().");

   typedef typename std::remove_extent<T>::type U;
   return std::unique_ptr<T>(new U[sizeof...(Args)]{std::forward<Args>(args)...});
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
   return make_unique_helper<T>(std::is_array<T>(), std::forward<Args>(args)...);
}

这可以在他的Core C ++ 6视频中看到。

STL的make_unique版本的更新版本现在可以作为N3656获得。该版本 C ++ 14草案采用


make_unique由Stephen T Lavalej提议在下一次标准更新中提出。
调子2012年


为什么对xeo进行所有不必要的编辑,一切都很好。该代码与Stephen T. Lavalej所说的完全一样,并且他在维护std库的dinkumware工作。您已经在那堵墙上发表了评论,所以您应该知道。
调子2012年

3
的实现make_unique应放在标头中。标头不应导入名称空间(请参见Sutter / Alexandrescu的“ C ++编码标准”书中的项目#59)。Xeo的更改有助于避免鼓励不良做法。
Bret Kuhns

不幸的是,我认为VC2010不支持VC2010,甚至我都不支持VC 2012
zhaorufei 2013年

19

std::make_shared不只是的简写std::shared_ptr<Type> ptr(new Type(...));。它做了一些您不能没有的事情。

为了完成其工作,std::shared_ptr除了保留用于实际指针的存储之外,还必须分配一个跟踪块。但是,由于std::make_shared分配了实际对象,因此有可能在同一内存块中同时std::make_shared分配对象跟踪块。

因此,虽然std::shared_ptr<Type> ptr = new Type(...);将有两个内存分配(一个为new,一个为std::shared_ptr跟踪块),但std::make_shared<Type>(...)将分配一个内存块。

对于许多潜在的用户来说,这一点很重要std::shared_ptr。唯一要做的std::make_unique就是稍微方便些。仅此而已。


2
不是必需的 提示,但不是必需的。
小狗

3
不仅是为了方便,在某些情况下还可以提高异常安全性。有关此信息,请参见Kerrek SB的答案。
Piotr99 2013年

19

虽然没有什么可以阻止您编写自己的帮助程序,但我相信make_shared<T>在库中提供的主要原因是,它实际上创建了与不同的内部指针类型shared_ptr<T>(new T),而不是分配了不同的内部指针,并且没有专用的方法就无法实现这一点。帮手。

make_unique另一方面,您的包装器只是一个new表达式周围的语法糖,因此虽然看起来很悦目,但它并没有带来任何new好处。 更正:事实并非如此:调用函数来包装new表达式可提供异常安全性,例如,在调用function的情况下void f(std::unique_ptr<A> &&, std::unique_ptr<B> &&)。拥有两个未new排序的原始值意味着如果一个新表达式失败并带有异常,则另一个表达式可能会泄漏资源。至于为什么没有make_unique标准:它只是被遗忘了。(这种情况偶尔发生。全球也没有std::cbegin即使应该有一个标准,标准。)

还要注意,它unique_ptr采用了您应该以某种方式允许的第二个模板参数;这与有所不同shared_ptr,后者使用类型擦除来存储自定义删除项而不使其成为该类型的一部分。


1
@FredOverflow:共享指针是一个相对复杂的类;内部保留一个多态参考控制块,但是有几种不同类型的控制块。shared_ptr<T>(new T)使用其中之一,make_shared<T>()使用另一种。允许这样做是一件好事,在某种意义上,make共享版本是您可以获得的最轻量的共享指针。
Kerrek SB 2011年

15
@FredOverflow:在创建时shared_ptr分配动态内存块以保持计数和“ disposer”操作shared_ptr。如果您显式传递指针,则它需要创建一个“新”块,如果使用make_shared它可以对象和卫星数据捆绑在一个内存块(一个new)中,从而可以更快地进行分配/重新分配,减少碎片,并且(通常)更好的缓存行为。
Matthieu M.

2
我认为我需要重新关注shared_ptr和朋友 ...
fredoverflow

4
-1“另一方面,您的make_unique包装器只是一个新表达式周围的语法糖,因此,尽管看上去令人赏心悦目,但它并没有带来新的东西。” 是错的。它带来了异常安全功能调用的可能性。但是,它不能保证。为此,它必须是类,以便可以声明该类的形式参数(Herb将其描述为选择加入与选择退出之间的区别)。
干杯和健康。-Alf 2012年

1
@ Cheersandhth.-Alf:是的,是的。从那以后我就意识到了这一点。我将编辑答案。
Kerrek SB 2012年

13

在C ++ 11 ...中(在模板代码中)也用于“包扩展”。

要求是将其用作包含未扩展参数包的表达式的后缀,并且它将仅将表达式应用于包的每个元素。

例如,以您的示例为基础:

std::forward<Args>(args)... -> std::forward<int>(1), std::forward<int>(2),
                                                     std::forward<int>(3)

std::forward<Args...>(args...) -> std::forward<int, int, int>(1,2,3)

我认为后者是不正确的。

另外,参数包可能不会传递给未扩展的函数。我不确定一包模板参数。


1
很高兴看到这个说明。为什么还不包括可变参数模板参数?
Kerrek SB 2011年

@Kerrek:因为我不太确定它的奇怪语法,而且我不知道是否有人玩过。所以我会继续保持我所知道的。如果某人有足够的动力和知识,那么它可能需要一个c ++ FAQ条目,因为可变句法非常完整。
Matthieu M.

语法为std::forward<Args>(args)...,扩展为forward<T1>(x1), forward<T2>(x2), ...
Kerrek SB 2011年

1
:我认为forward实际上总是需要模板参数,不是吗?
Kerrek SB 2011年

1
@霍华德:对!强制实例触发报警(ideone.com/GDNHb
马修M.

5

受Stephan T. Lavavej的实现启发,我认为拥有一个支持数组范围的make_unique可能很好,它位于github上,我很乐意对此发表评论。它允许您执行以下操作:

// create unique_ptr to an array of 100 integers
auto a = make_unique<int[100]>();

// create a unique_ptr to an array of 100 integers and
// set the first three elements to 1,2,3
auto b = make_unique<int[100]>(1,2,3); 
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.