首页 文章

make_unique和完美的转发

提问于
浏览
213

为什么标准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)... 究竟是什么意思?编译器对此做了什么?

6 回答

  • 19

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

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

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

  • 76

    在C 11中 ... 也用于(在模板代码中)"pack expansion" .

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

    例如, Build 在您的示例上:

    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)
    

    我认为后者是不正确的 .

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

  • 13

    受到Stephan T. Lavavej实现的启发,我认为拥有一个支持数组范围的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);
    
  • 5

    C标准化委员会主席Herb Sutter写道:blog

    C 11不包括make_unique部分是一种疏忽,几乎肯定会在将来添加 .

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

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

  • 154

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

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

相关问题