make_unique和完美的转发

fre*_*low 215 c++ unique-ptr variadic-templates perfect-forwarding c++11

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

std::unique_ptr<SomeUserDefinedType> p(new SomeUserDefinedType(1, 2, 3));
Run Code Online (Sandbox Code Playgroud)

有点冗长.以下不是更好吗?

auto p = std::make_unique<SomeUserDefinedType>(1, 2, 3);
Run Code Online (Sandbox Code Playgroud)

这隐藏得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)...));
}
Run Code Online (Sandbox Code Playgroud)

我花了很std::forward长时间来编译这些东西,但我不确定它是否正确.是吗?究竟是什么std::forward<Args>(args)...意思?编译器对此做了什么?

Joh*_*åde 157

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

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

他还提供了与OP给出的实现相同的实现.

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

  • ...我也不是在追求使用`make_unique`函数的安全性.首先,我不知道这个是不安全的,我不知道如何添加额外的类型会使它更安全*.我所知道的是,我感兴趣的是*理解*这个实现可以有的问题 - 我看不到任何 - ,以及替代实现如何解决它们.`make_unique`所依赖的*约定*是什么?您如何使用类型检查来强制安全?这两个问题我都希望得到答案. (3认同)
  • `make_unique`函数模板本身不保证异常安全调用.它依赖于约定,调用者使用它.相反,严格的静态类型检查(这是C++和C之间的主要区别)建立在*强制*安全性,通过类型的想法上.为此,`make_unique`可以只是一个类而不是函数.例如,请参阅我的[博客文章](http://alfps.wordpress.com/2010/05/15/cppx-c98-constructor-arguments-forwarding-v2-posting/#0x_standard_forwarding_example),从2010年5月开始.它也链接了来自Herb博客的讨论. (2认同)
  • @ Cheersandhth.-Alf:我在上面实现了*`make_unique`中的异常问题*,你指的是Sutter的一篇文章,我的google-fu指出的文章说'make_unique`提供了*强例外保证*,与您上述陈述相矛盾.如果您有不同的文章,我**有兴趣阅读它.所以我的原始问题是*如何`make_unique`(如上所定义)不是例外安全吗?*(旁注:是的,我确实认为`make_unique`*在可以应用的地方提高了*异常安全性) (2认同)

小智 77

很好,但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)...);
}
Run Code Online (Sandbox Code Playgroud)

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

STL版本的make_unique的更新版本现在可以作为N3656使用.这个版本 C++ 14采用.

  • `make_unique`的实现应该放在标题中.标题不应导入命名空间(参见Sutter/Alexandrescu的"C++编码标准"一书中的第59项).Xeo的变化有助于避免鼓励不良行为. (3认同)

Ker*_* SB 19

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

make_unique另一方面,你的包装器仅仅是一个new表达式的语法糖,所以尽管它看起来很悦目,但它并没有带来任何东西new. 更正:事实上并非如此:使用函数调用来包装new表达式可以提供异常安全性,例如在调用函数的情况下void f(std::unique_ptr<A> &&, std::unique_ptr<B> &&).有两个new相对于彼此未经过排序的原始s意味着如果一个新表达式失败并出现异常,则另一个表达式可能会泄漏资源.至于为什么make_unique标准中没有:它只是被遗忘了.(偶尔会发生这种情况.std::cbegin标准中也没有全球性,即使应该有一个.)

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

  • @FredOverflow:`shared_ptr`在创建`shared_ptr`时分配一块动态内存来保持计数和"处理器"操作.如果你明确地传递指针,它需要创建一个"new"块,如果你使用`make_shared`它可以将你的*对象和卫星数据捆绑在一个内存块(一个`new`)中,从而实现更快的分配/解除分配,减少碎片,并且(通常)更好的缓存行为. (15认同)
  • -1"另一方面,你的make_unique包装器仅仅是一个新表达式的语法糖,所以尽管它看起来很悦目,但它并没有给表带来任何新东西." 是错的.它带来了异常安全函数调用的可能性.但是,它没有带来保证; 为此,它需要是类,以便可以声明该类的正式参数(Herb将其描述为选择加入和选择退出之间的区别). (4认同)
  • 我想我需要重新观看[shared_ptr和朋友](http://channel9.msdn.com/Shows/Going+Deep/C9-Lectures-Stephan-T-Lavavej-Advanced-STL-1-of-n) ... (2认同)

Nic*_*las 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能做的就是稍微方便一点.没有比这更好的了.

  • 它不仅仅是为了方便,它还可以在某些情况下改善异常安全性.请参阅Kerrek SB的答案. (3认同)
  • 这不是必需的.暗示,但不是必需的. (2认同)

Mat*_* M. 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)
Run Code Online (Sandbox Code Playgroud)

我认为后者是不正确的.

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


Nat*_*ert 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); 
Run Code Online (Sandbox Code Playgroud)