为什么标准 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
不同。
make_unique
是没有意义的,因为显然它是通过普通旧 new
分配的,因此 必须 使用普通旧 delete
:)
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))
。
很好,但 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
的实现应放在标头中。标头不应导入命名空间(参见 Sutter/Alexandrescu 的“C++ 编码标准”一书中的 Item #59)。 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
唯一能做的就是稍微方便一些。仅此而已。
虽然没有什么能阻止您编写自己的帮助程序,但我认为在库中提供 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
不同,后者使用类型擦除来存储自定义删除器,而不使它们成为类型的一部分。
shared_ptr<T>(new T)
使用其中之一,make_shared<T>()
使用不同的。允许这是一件好事,并且 make-shared 版本在某种意义上是您可以获得的最轻量级的共享指针。
shared_ptr
在您创建 shared_ptr
时分配一块动态内存以保持计数和“处置器”操作。如果您显式传递指针,则需要创建一个“新”块,如果您使用 make_shared
,它可以将 your 对象和卫星数据捆绑在一个内存块中(一个 new
)导致更快的分配/释放、更少的碎片和(通常)更好的缓存行为。
在 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)
我认为后者是不正确的。
此外,参数包可能不会传递给未扩展的函数。我不确定一组模板参数。
std::forward<Args>(args)...
,扩展为 forward<T1>(x1), forward<T2>(x2), ...
。
forward
实际上总是需要一个模板参数,不是吗?
受到 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);
不定期副业成功案例分享
make_unique
函数模板本身不保证异常安全调用。它依赖于约定,调用者使用它。相比之下,严格的静态类型检查(这是 C++ 和 C 之间的主要区别)是建立在 enforcing 安全的思想之上的,通过类型。为此,make_unique
可以简单地是一个类而不是函数。例如,请参阅我 2010 年 5 月的 blog article。它还链接到 Herb 博客上的讨论。make_unique
设为类的建议,我现在认为最好将其设为产生make_unique_t
的函数,其原因是最不正当的解析存在问题:-)。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)))
...make_unique
,您将我指向 Sutter 的一篇文章,我的 google-fu 指向我的文章指出 {1 } 提供了强大的异常保证,这与您上面的陈述相矛盾。如果您有其他文章,我我有兴趣阅读它。所以我最初的问题是make_unique
(如上定义)如何不是异常安全的?(旁注:是的,我确实认为make_unique
改进了 某些地方的异常安全可以应用的地方)make_unique
功能。首先,我看不出这个是不安全的,我看不出添加一个额外的类型会如何使它更安全。我所知道的是,我对了解此实现可能存在的问题(我看不到任何问题)以及替代实现如何解决这些问题很感兴趣。make_unique
所依赖的约定是什么?您将如何使用类型检查来加强安全性?这是我很想回答的两个问题。