为什么std :: unique_ptr operator * throw和operator->不扔?

时间:2013-03-04 14:14:13

标签: c++ c++11 language-lawyer

在C ++标准草案(N3485)中,它陈述如下:

20.7.1.2.4 unique_ptr观察员[unique.ptr.single.observers]

typename add_lvalue_reference<T>::type operator*() const;

1 Requires: get() != nullptr.
2 Returns: *get().

pointer operator->() const noexcept;

3 Requires: get() != nullptr.
4 Returns: get().
5 Note: use typically requires that T be a complete type.

您可以看到operator*(取消引用)未指定为noexcept,可能是因为它可能导致段错误,但同一对象上的operator->被指定为{{1} }}。两者的要求是相同的,但异常规范存在差异。

我注意到它们有不同的返回类型,一个返回一个指针,另一个返回一个引用。那是说noexcept实际上没有取消引用任何东西吗?

事实上,在任何类型的NULL指针上使用operator->将是段错误(是UB)。那么,为什么其中一个被指定为operator->而另一个没有?

我确定我忽略了一些事情。

编辑:

noexcept我们有这个:

20.7.2.2.5 shared_ptr观察者[util.smartptr.shared.obs]

std::shared_ptr

不一样吗?这与不同的所有权语义有什么关系吗?

4 个答案:

答案 0 :(得分:26)

段错误不在C ++的异常系统中。如果取消引用空指针,则不会抛出任何类型的异常(嗯,至少如果您遵守Require:子句;请参阅下面的详细信息。)

对于operator->,它通常只是return m_ptr;(或return get(); unique_ptr)。如您所见,操作符本身不能抛出 - 它只返回指针。没有解除引用,没有任何东西。该语言对p->identifier

有一些特殊规则

§13.5.6 [over.ref] p1

  

如果x->m存在并且运算符被选为最佳运算符,则表达式(x.operator->())->m将被解释为xT类对象T::operator->()通过重载解析机制匹配函数(13.3)。

以上适用于递归,最后必须产生一个指针,使用内置的operator->。这允许智能指针和迭代器的用户只需执行smart->fun()而无需担心任何事情。

规范的Require:部分的注释:这些表示前提条件。如果你不满足它们,你就是在调用UB。

  

为什么然后,其中一个指定为noexcept而另一个不指定?

说实话,我不确定。似乎取消引用指针应始终为noexcept,但是,unique_ptr允许您完全更改内部指针类型(通过删除器)。现在,作为用户,您可以在operator*类型上为pointer定义完全不同的语义。也许它会在飞行中计算出来的东西?所有有趣的东西,可能会抛出。


  

看看std :: shared_ptr我们有这个:

这很容易解释 - shared_ptr不支持上面对指针类型的自定义,这意味着内置语义总是应用 - 和{{1} } *p p只是不抛出。

答案 1 :(得分:4)

对于它的价值,这里有一点历史,以及现在的情况如何。

在N3025之前,operator *未指定noexcept,但其描述确实包含Throws: nothing。此要求已在N3025中删除:

  

按指示更改[unique.ptr.single.observers](834)[有关详情,请参阅备注部分]:

     

typename add_lvalue_reference<T>::type operator*() const;
  1 - 要求:get() != 0 nullptr   2 - 返回:*get().
   3 - 投掷:没有。

以下是上述“备注”部分的内容:

  

在本文的评论过程中,如何正确指定operator *,operator []和异构比较函数的操作语义变得有争议。 [structure.specifications] / 3没有明确说明返回元素(在没有新的公式等价物的情况下)是否指定了效果。此外,还不清楚这是否允许这样的返回表达式通过异常退出,如果另外一个抛出:没有提供任何元素(实现者是否需要捕获那些?)。要解决此冲突,已删除任何现有的Throws元素以进行这些操作,这至少与[unique.ptr.special]和标准的其他部分一致。这样做的结果是我们现在提供了对潜在抛出比较函数的隐式支持,但不是针对齐= =和!=,这可能有点令人惊讶。

同一篇论文还包含了编辑operator ->定义的建议,但内容如下:

  

pointer operator->() const;
  4 - 要求:get() != 0 nullptr。
  5 - 返回:get()。
  6 - 投掷:什么都没有   7 - 注意:使用通常要求T是完整类型。

就问题本身而言:它归结为运算符本身与运算符使用的表达式之间的基本区别。

当你使用operator*时,操作符取消引用可以抛出的指针。

使用operator->时,运算符本身只返回一个指针(不允许抛出)。然后在包含->的表达式中取消引用该指针。取消引用指针的任何异常都发生在周围的表达式中而不是在运算符本身中。

答案 2 :(得分:1)

坦率地说,这对我来说只是一个缺陷。从概念上讲,a-> b应始终等效于(* a).b,即使a是智能指针,这也适用。但如果* a不是noexcept,则(* a).b不是,因此a-&gt; b不应该是。

答案 3 :(得分:-2)

关于:

  

那是说运营商 - &gt;实际上并没有取消引用任何东西?

不,->类型重载operator->的标准评估是:

a->b; // (a.operator->())->b

即。评估是递归定义的,当源代码包含->时,operator->被应用,产生另一个带有->的表达式,它本身可以引用operator-> ... < / p>

关于整体问题,如果指针为空,则行为未定义,缺少noexcept允许实现throw。如果签名为noexcept,则实施无法throwthrow将调用std::terminate。)