为什么`make_unique <t [n]>`不允许?</t [n]>

时间:2013-05-16 20:33:03

标签: c++ language-lawyer unique-ptr c++14

始终假设名称空间为std

C ++ 14委员会草案N3690定义std::make_unique因此:

  

[n3690: 20.9.1.4]: unique_ptr创建 [unique.ptr.create]

     

template <class T, class... Args> unique_ptr<T> make_unique(Args&&... args);

     

1 备注:除非T不是数组,否则此函数不应参与重载决策。
   2 返回:unique_ptr<T>(new T(std::forward<Args>(args)...)).

     

template <class T> unique_ptr<T> make_unique(size_t n);

     

3 备注:除非T是未知范围的数组,否则此函数不应参与重载决策。
   4 返回:unique_ptr<T>(new typename remove_extent<T>::type[n]()).

     

template <class T, class... Args> unspecified make_unique(Args&&...) = delete;

     

5 备注:除非T是已知绑定的数组,否则此函数不应参与重载解析。

现在,在我看来,这与泥浆一样清晰,我认为需要更多的阐述。但是,除了这篇评论之外,我相信我已经解释了每个变体的含义:

  1. template <class T, class... Args> unique_ptr<T> make_unique(Args&&... args);

    对于非数组类型,您的标准make_unique。据推测,“注释”表示某种形式的静态断言或SFINAE技巧是为了防止在T是数组类型时成功实例化模板。

    在高级别,将其视为等同于T* ptr = new T(args);的智能指针。

  2. template <class T> unique_ptr<T> make_unique(size_t n);

    数组类型的变体。创建一个动态分配的n×Ts数组,并将其包含在unique_ptr<T[]>中。

    在高级别,将其视为等同于T* ptr = new T[n];的智能指针。

  3. template <class T, class... Args> unspecified make_unique(Args&&...)

    不允许的。 “未指定”可能是unique_ptr<T[N]>

    否则智能指针会等同于无效的T[N]* ptr = new (keep_the_dimension_please) (the_dimension_is_constexpr) T[N];

  4. 首先,我是否正确?而且,如果是这样,第三个功能发生了什么?

    • 如果不允许程序员在为每个元素提供构造函数参数的同时动态分配数组(就像new int[5](args)是不可能的那样),那么第一个函数就已经涵盖了无法为数组类型实例化,不是吗?

    • 如果是为了阻止添加T[N]* ptr = new T[N]等构造语言(其中N有些constexpr),那么,为什么呢?存在unique_ptr<T[N]>包裹动态分配的N×T s块是不是完全可能的?如果委员会已经尽力拒绝使用make_unique来创建它,这会是一件坏事吗?

    为什么make_unique<T[N]>被禁止?

2 个答案:

答案 0 :(得分:33)

引自the original proposal

  

T[N]

     

自N3485起,unique_ptr不为T[N]提供部分专业化。       但是,用户很想写make_unique<T[N]>()。这个       是一个不赢的场景。返回unique_ptr<T[N]>会选择主要内容       单个对象的模板,这是奇怪的。返回unique_ptr<T[]>       将是另一个铁定的规则的例外       make_unique<something>()返回unique_ptr<something>。因此,这       提案使T[N]在这里格式不正确,允许实现发出       有用的static_assert消息。

该提案的作者Stephan T. Lavavej在this video on Core C++(由chris提供)中说明了这种情况,从1:01:10开始(或多或少)。

答案 1 :(得分:0)

对我来说,第三个重载看起来是多余的,因为它不会改变其他重载与T[N]不匹配的事实,并且似乎有助于生成更好的错误消息。请考虑以下实现:

template< typename T, typename... Args >
typename enable_if< !is_array< T >::value, unique_ptr< T > >::type
make_unique( Args&&... args )
{
  return unique_ptr< T >( new T( forward< Args >( args )... ) );
}

template< typename T >
typename enable_if< is_array< T >::value && extent< T >::value == 0, unique_ptr< T > >::type
make_unique( const size_t n )
{
  using U = typename remove_extent< T >::type;
  return unique_ptr< T >( new U[ n ]() );
}

当您尝试拨打std::make_unique<int[1]>(1)时,错误消息会将enable_if列为两个候选列表。如果添加第三个已删除的重载,则错误消息将列出三个候选项。此外,由于它被指定为=delete;,因此您无法在第三个重载的正文中提供更有意义的错误消息,例如static_assert(sizeof(T)==0,"array of known bound not allowed for std::make_shared");

如果你想玩它,可以使用live example

第三次超载在N3656和N3797中结束的事实可能是由于make_unique随着时间推移而发展的历史,但我想只有STL可以回答:)