为什么 std::unique_ptr 运算符*抛出而运算符>不抛出?
Why does std::unique_ptr operator* throw and operator-> does not throw?
在c++标准草案(N3485)中,它声明如下:
20.7.1.2.4 unique_ptr observers [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->
被指定为noexcept
。两者的需求是相同的,但是在异常规范方面存在差异。
我注意到它们有不同的返回类型,一个返回指针,另一个返回引用。这是不是说operator->
实际上没有引用任何东西?
问题的事实是,在任何类型的指针上使用operator->
是NULL,将段故障(是UB)。那么,为什么其中一个被指定为noexcept
而另一个没有呢?
我肯定我忽略了什么。
编辑:看std::shared_ptr
我们有这个:
20.7.2.2.5 shared_ptr观察员[util.smartptr.shared.obs]
T& operator*() const noexcept;
T* operator->() const noexcept;
不一样吗?这和不同的所有权语义有关系吗?
段错误不在c++的异常系统中。如果你解引用一个空指针,你不会得到任何类型的异常抛出(好吧,至少如果你遵守Require:
子句;
对于operator->
,它通常被简单地实现为return m_ptr;
(或unique_ptr
的return get();
)。如您所见,操作符本身不能抛出——它只返回指针。没有引用,什么都没有。该语言对p->identifier
有一些特殊的规则:
§13.5.6 [over.ref] p1
对于类型为
T
的类对象x
,如果T::operator->()
存在并且操作符被重载解析机制(13.3)选为最佳匹配函数,则表达式x->m
被解释为(x.operator->())->m
。
上面的操作递归地应用,最后必须产生一个指针,该指针使用内置的operator->
。这允许智能指针和迭代器的用户简单地执行smart->fun()
,而不用担心任何事情。
规范Require:
部分的注释:这些表示先决条件。如果您没有满足它们,则您正在调用UB。
那么为什么其中一个指定为noexcept而另一个指定为not呢?
老实说,我不确定。似乎对指针的解引用应该始终是noexcept
,然而,unique_ptr
允许您完全更改内部指针的类型(通过deleter)。现在,作为用户,您可以在您的pointer
类型上为operator*
定义完全不同的语义。也许它可以在飞行中进行计算?所有有趣的东西,这可能会扔。
看std::shared_ptr,我们有这个:
这很容易解释- shared_ptr
不支持上述对指针类型的自定义,这意味着内置语义总是适用- *p
(p
是T*
)根本不抛出。
无论如何,这里有一点历史,以及事情是如何发展到现在的。
在N3025之前,operator *
没有被指定为noexcept
,但是它的描述中确实包含了一个Throws: nothing
。在N3025:
按指示(834)更改[unique.ptr.single.observers][详细信息请参见备注部分]:
typename add_lvalue_reference<T>::type operator*() const;
1 -要求:get() !=
0nullptr
.
2 -返回:*get().
这是"评论"的内容。上面提到的部分:
在回顾本文的过程中,如何正确指定operator*、operator[]和异构比较函数的操作语义成为了争议。(结构。/3没有明确说明return元素(在没有新的Equivalent to公式的情况下)是否指定了效果。此外,还不清楚这是否允许这样的返回表达式通过异常退出,如果另外提供了一个Throws:-Nothing元素(是否需要实现者捕获这些?)为了解决这个冲突,为这些操作删除了任何现有的Throws元素,这至少与[unique.ptr.special]和标准的其他部分一致。这样做的结果是,我们现在隐式地支持潜在的抛出比较函数,但不支持同构的==和!=,这可能有点令人惊讶。
同一篇论文还包含了编辑operator ->
定义的建议,但它的内容如下:
pointer operator->() const;
4 -要求:get() !=
0nullptr.
5 -返回:get().
6 - throw: nothing.
7 -注意:使用时通常要求T为完整类型。
就问题本身而言:它归结为操作符本身与使用操作符的表达式之间的基本区别。
当使用operator*
时,操作符对指针解引用,这会抛出。
当您使用operator->
时,操作符本身只是返回一个指针(不允许抛出)。然后在包含->
的表达式中解引用该指针。指针解引用的任何异常都发生在周围的表达式中,而不是在操作符本身中。
坦率地说,这在我看来只是一个缺陷。从概念上讲,a->b应该总是等同于(*a)。B,即使a是智能指针,这也适用。但是如果*a不是noexcept,那么(*a)。B不是,因此a-> B也不应该是。
关于:
是不是说运算符->实际上并没有解引用任何东西?
不,->
对类型重载operator->
的标准求值是:
a->b; // (a.operator->())->b
。计算是递归定义的,当源代码包含->
时,应用operator->
产生另一个表达式,其中->
本身可以引用operator->
…
关于整个问题,如果指针为空,则行为未定义,并且缺少noexcept
允许实现throw
。如果签名是noexcept
,则实现不能throw
(throw
将调用std::terminate
)。
- 为什么比较运算符如此快速
- C++映射:具有自定义类的运算符[]不起作用(总是返回0)
- 使用C++中的模板和运算符重载执行矩阵运算
- 为什么这个运算符<重载函数对 STL 算法不可见?
- 增量运算符与后缀混淆
- 一个关于在C++中重载布尔运算符的问题
- 运算符C++ "delete []"仅删除 2 个前值
- 模板类无法识别友元运算符
- 我可以使用条件运算符初始化C风格的字符串文字吗
- 关闭||运算符优化
- 通过继承类使用来自不同命名空间的运算符
- C++Cast运算符过载
- 如何使用AngelScript注册SFML Vector2运算符
- 重载元组索引运算符-C++
- 如何使用重载的相等(==)运算符向测试用例添加描述
- 为什么Mat类的两个对象可以在不重载运算符+的情况下添加
- 多个If语句与使用逻辑运算符计算条件的单个语句的比较
- 布尔比较运算符是如何在C++中工作的
- 重载运算符new[]的行为取决于析构函数
- 是否需要使用 - &gt;运算符在C 中调用成员函数时