![](/img/trans.png)
[英]Under which circumstances might std::unique_ptr::operator[] throw?
[英]Why does std::unique_ptr operator* throw and operator-> does not throw?
在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->
指定為noexcept
。 兩者的要求相同,但是異常規范有所不同。
我注意到它們具有不同的返回類型,一種返回指針,另一種返回引用。 就是說operator->
實際上沒有取消引用任何東西嗎?
事實是,在任何類型為NULL的指針上使用operator->
都會導致段錯誤(即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::operator->()
存在並且如果選擇運算符作為最佳匹配函數,則表達式T
的類對象x
的表達式x->m
解釋為(x.operator->())->m
。通過過載解析機制(13.3)。
上面的內容遞歸適用,最后必須產生一個指針,為此使用了內置的operator->
。 這使智能指針和迭代器的用戶可以簡單地執行smart->fun()
而不用擔心任何事情。
需求的注釋Require:
規范的某些部分:這些表示前提條件。 如果您不認識他們,那就是在調用UB。
那么為什么將其中一個指定為noexcept而另一個則不指定呢?
老實說,我不確定。 取消引用指針似乎應該總是noexcept
,但是, unique_ptr
允許您完全更改內部指針類型(通過刪除器)。 現在,作為用戶,您可以在pointer
類型上為operator*
定義完全不同的語義。 也許它可以即時計算事物? 所有可能有趣的東西。
看std :: shared_ptr我們有這個:
這很容易解釋shared_ptr
不支持對指針類型的上述自定義,這意味着始終使用內置語義-並且*p
,其中p
是T*
根本不會拋出。
值得一提的是,這里有一些歷史,以及事情如何以現在的方式發展。
在N3025之前,沒有使用noexcept
指定operator *
,但是其描述確實包含Throws: nothing
noexcept
。 N3025中刪除了此要求:
按照指示更改[unique.ptr.single.observers](834)[有關詳細信息,請參見“備注”部分:
typename add_lvalue_reference<T>::type operator*() const;
1-要求:get() !=
0nullptr
。
2-返回:*get().
3-投擲:沒事。
這是上面提到的“備注”部分的內容:
在本文的審閱過程中,如何正確指定operator *,operator []和異構比較函數的操作語義引起了爭議。 [structure.specifications] / 3並未明確說明Return元素(在沒有新的Equivalent to公式的情況下)是否指定效果。 此外,還不清楚是否還允許通過異常返回這樣的return表達式,如果另外提供了Throws:-Nothing元素(是否需要實現者捕獲這些元素)。 為了解決此沖突,為這些操作刪除了任何現有的Throws元素,至少與[unique.ptr.special]和標准的其他部分一致。 結果是,我們現在隱式支持可能拋出比較函數,但不支持均質==和!=,這可能有點令人驚訝。
同一篇文章還包含有關編輯operator ->
的定義的建議,但內容如下:
pointer operator->() const;
4-要求:get() !=
0nullptr。
5-返回:get()。
6-投擲:沒事。
7-注意:使用通常要求T為完整類型。
就問題本身而言:它歸結為運算符本身和使用該運算符的表達式之間的基本區別。
當您使用operator*
,運算符將取消引用可能拋出的指針。
使用operator->
,運算符本身僅返回一個指針(不允許拋出該指針)。 然后,該指針在包含->
的表達式中被取消引用。 取消引用指針的任何異常都發生在周圍的表達式中,而不是在運算符本身中。
坦白說,這對我來說似乎是一個缺陷。 從概念上講,a-> b應該始終等於(* a).b,即使a是智能指針,這也適用。 但是,如果* a並非如此,則(* a).b並非如此,因此a-> b不應如此。
關於:
就是說operator->實際上沒有取消引用任何東西嗎?
不,對於類型重載operator->
->
的標准評估是:
a->b; // (a.operator->())->b
即,評價是遞歸定義中,當源代碼包含一個->
, operator->
與被施加產生另一種表達->
可以本身指的是operator->
...
關於整個問題,如果指針為null,則行為是未定義的,缺少noexcept
導致實現throw
。 如果簽名為noexcept
則實現無法throw
( throw
將是對std::terminate
的調用)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.