【问题标题】:Why does std::unique_ptr operator* throw and operator-> does not throw?为什么 std::unique_ptr operator* 会抛出而 operator-> 不会抛出?
【发布时间】:2013-02-18 15:47:47
【问题描述】:

在 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,可能是因为它会导致segfault,但是随后同一个对象上的operator-&gt;被指定为noexcept。两者的要求相同,但异常规范有所不同。

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

事实上,在任何类型的 NULL 指针上使用 operator-&gt; 都会出现段错误(是 UB)。那么,为什么其中一个被指定为noexcept 而另一个不是?

我确定我忽略了一些东西。

编辑:

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

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

T& operator*() const noexcept;

T* operator->() const noexcept;

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

【问题讨论】:

  • "这是说 operator-> 实际上并没有取消引用任何东西?"是的,就是这样
  • 也许是允许实现在选择时抛出空指针取消引用。

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


【解决方案1】:

段错误在 C++ 的异常系统之外。如果您取消引用空指针,则不会引发任何类型的异常(好吧,至少如果您遵守 Require: 子句;请参阅下文了解详细信息)。

对于operator-&gt;,它通常实现为简单的return m_ptr;(或return get(); 用于unique_ptr)。如您所见,运算符本身不能抛出 - 它只是返回指针。没有解除引用,什么都没有。该语言对p-&gt;identifier 有一些特殊规则:

§13.5.6 [over.ref] p1

如果 T::operator-&gt;() 存在并且运算符被重载解析机制选择为最佳匹配函数 (13.3) .

以上内容递归应用,最后必须产生一个指针,使用内置的operator-&gt;。这使得智能指针和迭代器的用户可以简单地执行smart-&gt;fun() 而无需担心任何事情。

规范中Require: 部分的注释:这些表示前提条件。如果你没有遇到他们,你就是在调用 UB。

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

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


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

这很容易解释 - shared_ptr 不支持上述对指针类型的自定义,这意味着内置语义始终适用 - 和 *p where @987654342 @ 是 T* 根本不会抛出。

【讨论】:

  • 你可以改变内部指针类型,但我不认为这是因为删除器。如果该类型存在,则指针为“std::remove_reference&lt;D&gt;::type::pointer,否则为 T*”
  • 不过,operator* 过载的可能性可能是正确的。
  • @Stephen: D 是删除器类型,如果它定义了嵌套的pointer 类型,则使用该类型。
  • @Red:它可能包含一个删除器,但该删除器对pointer 类型没有任何影响。加入我的 Lounge 聊天室了解更多信息。
  • 抛出 unique_ptr 取消引用的一个用例是在 null 取消引用时抛出的自定义指针类型。
【解决方案2】:

对于它的价值,这里有一点历史,以及事情是如何变成现在这样的。

在 N3025 之前,operator * 没有与 noexcept 一起指定,但它的描述确实包含 Throws: nothing。此要求已在 N3025 中删除:

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

typename add_lvalue_reference&lt;T&gt;::type operator*() const;
1 - 要求:get() != 0nullptr
2 - 返回:*get().
3 - 抛出:无。

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

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

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

pointer operator-&gt;() const;
4 - 要求:get() != 0nullptr.
5 - 返回:get()。
6 - 抛出:没有。
7 - 注意:使用通常要求 T 是一个完整的类型。

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

当您使用operator* 时,运算符会取消对指针的引用,这可能会抛出。

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

    【解决方案4】:

    关于:

    是说 operator-> 实际上并没有取消引用任何东西吗?

    不,对于重载 operator-&gt; 的类型,-&gt; 的标准评估是:

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

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

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

    【讨论】:

    • 那么(a.operator-&gt;())的类型是什么?
    • 行为未定义已经允许实现抛出,即使函数是noexcept
    • @TonyTheLion: 无论你定义它是什么......它都可以产生一个指针或一个对象,如果它产生一个对象,operator-&gt; 将被调用以便a-&gt;b 被翻译进入((a.operator-&gt;())-&gt;operator-&gt;())b,除非第二次评估也产生一个非指针,在这种情况下......
    • @R.MartinhoFernandes:不,不是真的。合约要求它不抛出,编译器会生成捕获异常的代码并调用std::terminate()。调用 nothrow 函数的结果永远不会异常。
    • @David 未定义行为意味着标准对行为没有要求。这意味着它可以很好地抛出空解引用,而不管 noexcept。契约要求它不抛出,它还要求你不要将它应用于空指针。只有遵守合同,您才能期望得到履行。
    猜你喜欢
    • 2016-07-16
    • 1970-01-01
    • 2012-01-16
    • 1970-01-01
    • 2012-10-19
    • 1970-01-01
    • 2014-06-22
    相关资源
    最近更新 更多