make_unique完美转发

make_unique and perfect forwarding

提问人:fredoverflow 提问时间:8/12/2011 最后编辑:Xeofredoverflow 更新时间:9/5/2016 访问量:73543

问:

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

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::forwardstd::forward<Args>(args)...

C++ C++11 变量模板 unique-ptr 完美转发

评论

1赞 Kerrek SB 8/12/2011
可以肯定的是,我们之前有过这样的讨论......另请注意,这需要第二个模板参数,您应该以某种方式允许该参数 - 这与 .unique_ptrshared_ptr
5赞 fredoverflow 8/12/2011
@Kerrek:我认为使用自定义删除器进行参数化是没有意义的,因为显然它通过普通的旧进行分配,因此必须使用普通的旧:)make_uniquenewdelete
1赞 Kerrek SB 8/12/2011
@Fred:没错。因此,拟议的将仅限于分配......好吧,如果你想写它也没关系,但我明白为什么这样的东西不是标准的一部分。make_uniquenew
4赞 Alexandre C. 8/12/2011
实际上,我喜欢使用模板,因为 的构造函数是显式的,因此从函数返回很冗长。另外,我宁愿使用.make_uniquestd::unique_ptrunique_ptrauto p = make_unique<foo>(bar, baz)std::unique_ptr<foo> p(new foo(bar, baz))
5赞 stefan 5/9/2013
make_unique进来了,看 isocpp.org/blog/2013/04/trip-report-iso-c-spring-2013-meetingC++14

答:

19赞 Kerrek SB 8/12/2011 #1

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

另一方面,你的make_unique包装器只是围绕一个新表达的句法糖,所以虽然它可能看起来很赏心悦目,但它并没有带来任何新的东西 更正:这实际上并非如此:使用函数调用来包装表达式可提供异常安全性,例如在调用函数的情况下。如果两个原始表达式彼此未排序,则意味着如果一个新表达式因异常而失败,另一个表达式可能会泄漏资源。至于为什么标准中没有:它只是被遗忘了。(这种情况偶尔会发生。标准中也没有全局,即使应该有一个。newvoid f(std::unique_ptr<A> &&, std::unique_ptr<B> &&)newmake_uniquestd::cbegin

另请注意,这需要第二个模板参数,您应该以某种方式允许该参数;这与 不同,后者使用类型擦除来存储自定义删除程序,而不使它们成为类型的一部分。unique_ptrshared_ptr

评论

1赞 Kerrek SB 8/12/2011
@FredOverflow:共享指针是一个相对复杂的类;在内部,它保留了一个多态参考控制块,但有几种不同类型的控制块。 使用其中之一,使用不同的。允许这样做是一件好事,从某种意义上说,make-shared 版本是你能得到的最轻量级的共享指针。shared_ptr<T>(new T)make_shared<T>()
15赞 Matthieu M. 8/12/2011
@FredOverflow:分配一个动态内存块,以便在创建 .如果显式传递指针,则需要创建一个“新”块,如果使用它,可以对象和卫星数据捆绑在单个内存块(一个)中,从而实现更快的分配/释放,减少碎片和(通常)更好的缓存行为。shared_ptrshared_ptrmake_sharednew
2赞 fredoverflow 8/12/2011
我想我需要重新观看shared_ptr和朋友们......
4赞 Cheers and hth. - Alf 5/12/2012
-1 “另一方面,你的make_unique包装器只是围绕一个新表达的句法糖,所以虽然它可能看起来很赏心悦目,但它并没有带来任何新的东西。它带来了异常安全函数调用的可能性。然而,它并不能带来保证;为此,它需要是类,以便可以声明该类的正式参数(Herb 将其描述为选择加入和选择退出之间的区别)。
1赞 Kerrek SB 5/13/2012
@Cheersandhth.-Alf:是的,这是真的。从那以后,我意识到了这一点。我会编辑答案。
19赞 Nicol Bolas 8/12/2011 #2

std::make_shared不仅仅是 的简写。它做了一些你离不开它的事情。std::shared_ptr<Type> ptr(new Type(...));

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

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

这对 .唯一要做的就是稍微方便一点。仅此而已。std::shared_ptrstd::make_unique

评论

2赞 Puppy 8/12/2011
这不是必需的。提示,但不是必需的。
3赞 Piotr99 3/21/2013
这不仅是为了方便,而且在某些情况下还可以提高异常安全性。请参阅Kerrek SB对此的回答。
13赞 Matthieu M. 8/12/2011 #3

在 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 8/12/2011
很高兴看到这一点。为什么不也包括可变参数模板参数呢?
0赞 Matthieu M. 8/12/2011
@Kerrek:因为我不太确定它奇怪的语法,也不知道有没有很多人玩过。所以我会坚持我所知道的。如果有人有动力和知识渊博,它可能需要一个 c++ FAQ 条目,因为可语法非常完整。
0赞 Kerrek SB 8/12/2011
语法是 ,它扩展为 。std::forward<Args>(args)...forward<T1>(x1), forward<T2>(x2), ...
1赞 Kerrek SB 8/13/2011
:我认为实际上总是需要一个模板参数,不是吗?forward
1赞 Matthieu M. 8/14/2011
@Howard:对!强制实例化会触发警告 (ideone.com/GDNHb)
161赞 Johan Råde 3/12/2012 #4

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

C++ 11 没有包含的部分是一个疏忽,几乎肯定会在未来添加。make_unique

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

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

评论

2赞 Cheers and hth. - Alf 5/12/2012
函数模板本身并不能保证异常安全调用。它依赖于约定,即调用方使用它。相比之下,严格的静态类型检查(这是 C++ 和 C 之间的主要区别)建立在通过类型强制执行安全性的思想之上。为此,可以简单地是一个类而不是函数。例如,请参阅我 2010 年 5 月的博客文章。它也链接到 Herb 博客上的讨论。make_uniquemake_unique
1赞 Cheers and hth. - Alf 7/16/2012
@DavidRodríguez-dribeas:阅读 Herb 的博客,了解异常安全问题。忘掉“不是为了衍生而设计的”,那只是垃圾。与其建议我创建一个类,不如说我现在认为最好让它成为一个产生一个 的函数,其原因是最反常的解析存在问题:-)。make_uniquemake_unique_t
1赞 David Rodríguez - dribeas 7/16/2012
@Cheersandhth.-Alf:也许我们读过另一篇文章,因为我刚刚读到的那篇文章明确指出,它提供了强大的例外保证。或者,也许您正在将工具与其使用混合在一起,在这种情况下,没有任何功能是异常安全的。考虑一下,显然提供了保证,但根据您的推理,它不是例外安全的,因为它可能会被滥用。更糟糕的是,也不例外安全!make_uniquevoid f( int *, int* ){}no throwvoid f( int, int ) {}typedef unique_ptr<int> up; f( *up(new int(5)), *up(new int(10)))
2赞 David Rodríguez - dribeas 7/17/2012
@Cheersandhth.-Alf:我问了如上所述实施异常问题,你向我指出了 Sutter 的一篇文章,我的 google-fu 向我指出了提供强异常保证的州的文章,这与你上面的陈述相矛盾。如果您有不同的文章,有兴趣阅读它。所以我最初的问题是:make_unique(如上所述)如何不是异常安全的? (旁注:是的,我确实认为这提高了可以应用它的地方的异常安全性)make_uniquemake_uniquemake_unique
3赞 David Rodríguez - dribeas 7/17/2012
...我也不是在追求一个不太安全的功能。首先,我看不出这个是不安全的,我看不出添加额外的类型会如何使它更安全。我所知道的是,我有兴趣了解这个实现可能存在的问题——我看不到任何问题--,以及替代实现将如何解决这些问题。取决于哪些约定?您将如何使用类型检查来加强安全性?这是我希望得到答案的两个问题。make_uniquemake_unique
80赞 tominator 11/22/2012 #5

很好,但 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)...);
}

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

STL的make_unique版本的更新版本现在以N3656的形式提供。这个版本被C++草案14采用

评论

0赞 tominator 11/25/2012
make_unique由 Stephen T Lavalej 在下一个 std 更新中提出。
0赞 tominator 11/25/2012
这是对他谈论添加它的喜欢。channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-/......
0赞 tominator 11/26/2012
为什么要进行所有不必要的编辑 xeo,它很好。该代码与 Stephen T. Lavalej 所说的一模一样,他为维护 std 库的 dinkumware 工作。你已经在那面墙上评论过了,所以你应该知道。
3赞 Bret Kuhns 12/15/2012
的实现应该放在标头中。标头不应导入命名空间(请参阅 Sutter/Alexandrescu 的“C++ 编码标准”一书中的项 #59)。Xeo 的更改有助于避免鼓励不良做法。make_unique
0赞 zhaorufei 1/8/2013
不幸的是,VC2010 不支持,我认为即使是 VC2012,它们都不支持变量温度参数
5赞 Nathan Binkert 1/3/2013 #6

受到 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);