簡體   English   中英

為什么std :: unique_ptr運算符*拋出而運算符->不拋出?

[英]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 ,其中pT*根本不會拋出。

值得一提的是,這里有一些歷史,以及事情如何以現在的方式發展。

在N3025之前,沒有使用noexcept指定operator * ,但是其描述確實包含Throws: nothing noexcept 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並未明確說明Return元素(在沒有新的Equivalent to公式的情況下)是否指定效果。 此外,還不清楚是否還允許通過異常返回這樣的return表達式,如果另外提供了Throws:-Nothing元素(是否需要實現者捕獲這些元素)。 為了解決此沖突,為這些操作刪除了任何現有的Throws元素,至少與[unique.ptr.special]和標准的其他部分一致。 結果是,我們現在隱式支持可能拋出比較函數,但不支持均質==和!=,這可能有點令人驚訝。

同一篇文章還包含有關編輯operator ->的定義的建議,但內容如下:

pointer operator->() const;
4-要求: get() != 0 nullptr。
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則實現無法throwthrow將是對std::terminate的調用)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM