ChatGPT解决这个技术问题 Extra ChatGPT

make_unique 和完美的转发

为什么标准 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::forward 的东西,但我不确定它是否正确。是吗? std::forward<Args>(args)... 到底是什么意思?编译器对此有何看法?

很确定我们之前已经进行过讨论……还请注意,unique_ptr 采用第二个模板参数,您应该以某种方式允许 - 这与 shared_ptr 不同。
@Kerrek:我认为使用自定义删除器参数化 make_unique 是没有意义的,因为显然它是通过普通旧 new 分配的,因此 必须 使用普通旧 delete :)
@Fred:是的。因此,建议的 make_unique 将仅限于 new 分配...好吧,如果您想编写它也没关系,但我明白为什么这样的东西不是标准的一部分。
实际上,我喜欢使用 make_unique 模板,因为 std::unique_ptr 的构造函数是显式的,因此从函数返回 unique_ptr 很冗长。另外,我宁愿使用 auto p = make_unique<foo>(bar, baz) 而不是 std::unique_ptr<foo> p(new foo(bar, baz))
make_unique 即将进入 C++14,请参阅 isocpp.org/blog/2013/04/trip-report-iso-c-spring-2013-meeting

I
Ivan Perevezentsev

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

C++11 不包含 make_unique 部分是疏忽,而且几乎肯定会在未来添加。

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

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


make_unique 函数模板本身不保证异常安全调用。它依赖于约定,调用者使用它。相比之下,严格的静态类型检查(这是 C++ 和 C 之间的主要区别)是建立在 enforcing 安全的思想之上的,通过类型。为此,make_unique 可以简单地是一个类而不是函数。例如,请参阅我 2010 年 5 月的 blog article。它还链接到 Herb 博客上的讨论。
@DavidRodríguez-dribeas:阅读 Herb 的博客以了解异常安全问题。忘记“不是为派生而设计的”,那只是垃圾。而不是我将 make_unique 设为类的建议,我现在认为最好将其设为产生 make_unique_t 的函数,其原因是最不正当的解析存在问题:-)。
@Cheersandhth.-Alf:也许我们已经阅读了另一篇文章,因为我刚刚read 明确指出 make_unique 提供了强大的异常保证。或者,也许您将工具与它的用途混合在一起,在这种情况下 no 函数是异常安全的。考虑 void f( int *, int* ){},显然提供了 no throw 保证,但根据您的推理,它不是异常安全的,因为它可能被滥用。更糟糕的是,void f( int, int ) {} 也不是异常安全的!:typedef unique_ptr<int> up; f( *up(new int(5)), *up(new int(10)))...
@Cheersandhth.-Alf:我询问了上述实现的异常问题in make_unique,您将我指向 Sutter 的一篇文章,我的 google-fu 指向我的文章指出 {1 } 提供了强大的异常保证,这与您上面的陈述相矛盾。如果您有其他文章,我有兴趣阅读它。所以我最初的问题是 make_unique(如上定义)如何不是异常安全的?(旁注:是的,我确实认为 make_unique 改进了 某些地方的异常安全可以应用的地方)
...我也不追求更安全的使用 make_unique 功能。首先,我看不出这个是不安全的,我看不出添加一个额外的类型会如何使它更安全。我所知道的是,我对了解此实现可能存在的问题(我看不到任何问题)以及替代实现如何解决这些问题很感兴趣。 make_unique 所依赖的约定是什么?您将如何使用类型检查来加强安全性?这是我很想回答的两个问题。
B
Bruno De Fraine

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

这可以在 his Core C++ 6 video 上看到。

STL 的 make_unique 版本的更新版本现已作为 N3656 提供。此版本 got adopted 为 C++14 草案。


make_unique 由 Stephen T Lavalej 提出,用于下一个标准更新。
这是一个喜欢他谈论添加它的内容。 channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-/…
为什么要进行所有不必要的编辑xeo,它本来就很好。该代码与 Stephen T. Lavalej 所说的完全相同,他为维护 std 库的 dinkumware 工作。你已经对那面墙发表了评论,所以你应该知道。
make_unique 的实现应放在标头中。标头不应导入命名空间(参见 Sutter/Alexandrescu 的“C++ 编码标准”一书中的 Item #59)。 Xeo 的更改有助于避免鼓励不良做法。
不幸的是,VC2010不支持,甚至我认为VC2012都不支持可变模板参数
X
Xeo

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 唯一能做的就是稍微方便一些。仅此而已。


这不是必需的。提示,但不是必需的。
这不仅是为了方便,而且在某些情况下还可以提高异常安全性。请参阅 Kerrek SB 对此的回答。
K
Kerrek SB

虽然没有什么能阻止您编写自己的帮助程序,但我认为在库中提供 make_shared<T> 的主要原因是它实际上创建了与 shared_ptr<T>(new T) 不同的内部共享指针类型,它的分配方式不同,而且没有办法无需专门的助手即可实现这一目标。

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

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


@FredOverflow:共享指针是一个比较复杂的类;在内部,它保留了一个多态引用控制块,但有几种不同类型的控制块。 shared_ptr<T>(new T) 使用其中之一,make_shared<T>() 使用不同的。允许这是一件好事,并且 make-shared 版本在某种意义上是您可以获得的最轻量级的共享指针。
@FredOverflow:shared_ptr 在您创建 shared_ptr 时分配一块动态内存以保持计数和“处置器”操作。如果您显式传递指针,则需要创建一个“新”块,如果您使用 make_shared,它可以将 your 对象和卫星数据捆绑在一个内存块中(一个 new)导致更快的分配/释放、更少的碎片和(通常)更好的缓存行为。
我想我需要重新观看shared_ptr and friends...
-1 “另一方面,您的 make_unique 包装器只是围绕新表达式的语法糖,因此虽然它看起来可能令人赏心悦目,但它并没有带来任何新东西。”是错的。它带来了异常安全函数调用的可能性。但是,它并不能带来保证;为此,它需要是类,以便可以声明该类的正式参数(Herb 将其描述为选择加入和选择退出之间的区别)。
@Cheersandhth.-Alf:是的,这是真的。从那以后我意识到了这一点。我会编辑答案。
J
Johan Råde

在 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)

我认为后者是不正确的。

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


很高兴看到这一点。为什么不包括可变参数模板参数呢?
@Kerrek:因为我不太确定它奇怪的语法,也不知道有没有很多人玩过。所以我会坚持我所知道的。如果有人有足够的动力和知识,它可能需要一个 c++ FAQ 条目,因为可变参数语法非常完整。
语法为 std::forward<Args>(args)...,扩展为 forward<T1>(x1), forward<T2>(x2), ...
:我认为 forward 实际上总是需要一个模板参数,不是吗?
@霍华德:对!强制实例化会触发警告 (ideone.com/GDNHb)
N
Nathan Binkert

受到 Stephan T. Lavavej 实现的启发,我认为拥有一个支持数组范围 it's on github 的 make_unique 可能会很好,我很想得到关于它的评论。它允许您这样做:

// 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);