【问题标题】:Why is `make_unique<T[N]>` disallowed?为什么禁止使用`make_unique<T[N]>`?
【发布时间】:2013-05-11 21:47:33
【问题描述】:

始终使用命名空间std

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

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

template &lt;class T, class... Args&gt; unique_ptr&lt;T&gt; make_unique(Args&amp;&amp;... args);

1 备注:除非T不是数组,否则该函数不参与重载解析。
2返回:unique_ptr&lt;T&gt;(new T(std::forward&lt;Args&gt;(args)...)).

template &lt;class T&gt; unique_ptr&lt;T&gt; make_unique(size_t n);

3 备注:除非T是一个未知边界的数组,否则该函数不参与重载解析。
4返回:unique_ptr&lt;T&gt;(new typename remove_extent&lt;T&gt;::type[n]()). p>

template &lt;class T, class... Args&gt; unspecified make_unique(Args&amp;&amp;...) = delete;

5 备注:除非T是一个已知边界的数组,否则该函数不参与重载解析。

现在,在我看来,这就像泥巴一样清晰,我认为它需要更多的阐述。但是,除了这篇社论评论,我相信我已经解码了每个变体的含义:

  1. template &lt;class T, class... Args&gt; unique_ptr&lt;T&gt; make_unique(Args&amp;&amp;... args);

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

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

  2. template &lt;class T&gt; unique_ptr&lt;T&gt; make_unique(size_t n);

    数组类型的变体。创建一个动态分配的 n × Ts 数组,并将其包装在 unique_ptr&lt;T[]&gt; 中返回。

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

  3. template &lt;class T, class... Args&gt; unspecified make_unique(Args&amp;&amp;...)

    不允许。 “未指定”可能是unique_ptr&lt;T[N]&gt;

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

首先,我说的对吗?如果是这样,第三个函数是怎么回事?

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

  • 如果它是为了防止像T[N]* ptr = new T[N](其中N 是一些constexpr)这样的构造语言添加到语言中,那么,为什么? unique_ptr&lt;T[N]&gt; 包装动态分配的N × Ts 块不是完全有可能存在吗?这会是一件坏事吗,以至于委员会已经竭尽全力禁止使用make_unique 创建它?

为什么不允许make_unique&lt;T[N]&gt;

【问题讨论】:

  • "这个函数应该参与重载决议除非T是不是一个数组。" - 是的...
  • 我认为答案是here
  • @Mehrdad:不是草案,而是新标准……
  • @Mehrdad:我同意!幸运的是,您的建议很容易遵循,因为到目前为止还没有人这样做。
  • 而不是T[N]* ptr = new (keep_the_dimension_please) (the_dimension_is_constexpr) T[N],您将使用不太符合的代码:T (*ptr)[N] = reinterpret_cast&lt;T(*)[N]&gt;(new T[N]); 不符合是因为,尽管有一条特殊规则说您可以将指针转换为第一个元素从标准布局结构到指向结构的指针,数组没有类似的规则。但是,此代码可跨实际实现移植。

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


【解决方案1】:

引用the original proposal:

T[N]

从 N3485 开始,unique_ptr 不提供 T[N] 的部分特化。 但是,用户会很想写make_unique&lt;T[N]&gt;()。这 是一个双赢的局面。返回 unique_ptr&lt;T[N]&gt; 将选择主要的 单个对象的模板,这很奇怪。返回unique_ptr&lt;T[]&gt; 将是一个例外,否则铁定的规则 make_unique&lt;something&gt;() 返回unique_ptr&lt;something&gt;。因此,这 提案使T[N] 在这里格式错误,允许实现发出 有用的static_assert 消息。

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

【讨论】:

【解决方案2】:

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

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&lt;int[1]&gt;(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 可以回答这个问题:)

【讨论】:

  • 致删除评论的评论者:错误消息因编译器而异,但对于 GCC/Clang 来说,最好使用static_assert 进行第三次重载以获取short and to-the-point error messages
  • 我玩过现场示例。使用 3 个重载:error: call to deleted function 'make_unique'(+ 一个“候选函数 (...) 已被显式删除”注释 + 2“候选模板被忽略:被 'enable_if' 禁用”注释)。只有前 2 个重载:error: no matching function for call to 'make_unique',我觉得这更令人困惑(+ 2“候选模板被忽略:被 'enable_if' 禁用”注释)。 (这适用于 clang++ (3.4);对于 g++ (4.8),当您删除第三个重载时,实际上错误消息会变得 longer。)现在 static_assert 确实可能会更好。
  • @gx_ 我想知道标准是否应该只允许实现提供额外的重载,这些重载要么被删除,要么通常具有适当的static_assert,以改善错误消息——不仅在这个特定的例子中,而是在任何地方...
  • 第二次考虑static_assert 如果你用decltype( experimental::make_unique&lt;int[1]&gt;(42) ) * p = nullptr;之类的东西替换你的实例的主体,那么用static_assert替代它会编译没有错误,而使用=delete(或删除第三个重载时)会导致预期的错误。 编辑: 所以关于 17.6.5.4,这似乎是一个可观察到的行为差异
  • @gx_ 请注意,17.6.5.4/2 明确以 "A call to..." 开头,另见脚注 187)非调用/未评估的上下文可能会看到额外的重载。也就是说,为make_unique 明确指定第三个重载似乎适得其反。
猜你喜欢
  • 2019-10-09
  • 2017-03-19
  • 1970-01-01
  • 2019-05-21
  • 2023-03-30
  • 2023-02-25
  • 2019-05-27
  • 2011-08-03
相关资源
最近更新 更多