35

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*(dereference) が として指定されていないことがわかりますnoexcept。これはおそらく segfault を引き起こす可能性があるためですがoperator->、同じオブジェクトで が として指定されていnoexceptます。両方の要件は同じですが、例外仕様に違いがあります。

戻り値の型が異なることに気付きました.1つはポインターを返し、もう1つは参照を返します。operator->それは実際には何も逆参照しないということですか?

問題は、operator->NULL である任意の種類のポインターを使用すると、segfault (UB) が発生することです。では、なぜこれらの 1 つが指定されnoexcept、もう 1 つが指定されていないのでしょうか。

私は何かを見落としていると確信しています。

編集:

これを見ると、次のようになりstd::shared_ptrます。

20.7.2.2.5 shared_ptr オブザーバー [util.smartptr.shared.obs]

T& operator*() const noexcept;

T* operator->() const noexcept;

それは同じではありませんか?それは、異なる所有権のセマンティクスと関係がありますか?

4

4 に答える 4

28

segfault は、C++ の例外システムの外側にあります。null ポインターを逆参照する場合、いかなる種類の例外もスローされません (少なくとも、Require:節に準拠している場合。詳細については以下を参照してください)。

for operator->、通常は単純にreturn m_ptr;(またはreturn get();for unique_ptr) として実装されます。ご覧のとおり、オペレーター自体はスローできません。ポインターを返すだけです。逆参照も何もありません。この言語には、次のような特別な規則がありp->identifierます。

§13.5.6 [over.ref] p1

式は、存在する場合、およびオーバーロード解決メカニズム (13.3) によって最適一致関数として演算子が選択された場合、型のクラス オブジェクトx->mとして解釈されます。(x.operator->())->mxTT::operator->()

上記は再帰的に適用され、最終的にビルトインoperator->が使用されるポインターを生成する必要があります。これにより、スマート ポインターとイテレーターのユーザーはsmart->fun()何も心配することなく簡単に実行できます。

仕様の一部に関する注意Require:: これらは前提条件を示します。それらに会わない場合は、UB を呼び出しています。

では、なぜこれらの 1 つが noexcept として指定され、もう 1 つが指定されていないのでしょうか。

正直なところ、よくわかりません。ポインターの逆参照は常に である必要があるように思われますがnoexcept、内部ポインターの型を (deleter を介して)unique_ptr完全に変更することができます。これで、ユーザーとして、型に対してまったく異なるセマンティクスを定義できoperator*ますpointer。多分それはその場で物事を計算しますか?スローされる可能性のあるすべての楽しいもの。


std::shared_ptr を見ると、次のようになります。

これは簡単に説明できます -shared_ptrポインタ型に対する上記のカスタマイズをサポートしていません。つまり、組み込みのセマンティクスが常に適用されます - そして*pwherepT*単純にスローしません。

于 2013-03-04T14:47:42.833 に答える
4

価値のあるものとして、ここに歴史の一部と、物事が現在のようになった方法を示します.

N3025 より前operator *は で指定されていませんでしnoexceptたが、その説明にはThrows: nothing. この要件は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 は、Returns 要素 (新しい同等の式がない場合) が効果を指定するかどうかを明確に述べていません。さらに、Throws:-Nothing 要素が追加で提供されている場合、そのような return 式が例外を介して終了できるかどうかは不明です (実装者はそれらをキャッチする必要がありますか?)。この競合を解決するために、これらの操作の既存の Throws 要素はすべて削除されました。これは、少なくとも [unique.ptr.special] および標準の他の部分と一致しています。この結果、潜在的に比較関数をスローすることを暗黙的にサポートするようになりましたが、同種の == と != はサポートしていません。

同じ論文には、 の定義を編集するための推奨事項も含まれてoperator ->いますが、次のように書かれています。

pointer operator->() const;
4 - 必須: get() != 0 nullptr。
5 - 戻り値: get()。
6 - スロー: なし。
7 - 注: 通常、T は完全な型である必要があります。

質問自体に関する限り、演算子自体と演算子が使用されている式との基本的な違いに帰着します。

を使用するoperator*と、オペレーターはポインターを逆参照し、スローすることができます。

を使用するoperator->と、演算子自体はポインターを返すだけです (これはスローできません)。そのポインターは、->. ポインターの逆参照による例外は、演算子自体ではなく、周囲の式で発生します。

于 2014-02-09T01:02:32.570 に答える
1

率直に言って、これは私には欠陥のように見えます。概念的には、a->b は常に (*a).b と同等である必要があり、これは a がスマート ポインターであっても適用されます。しかし、*a が noexcept でない場合、(*a).b はそうではないため、a->b はすべきではありません。

于 2013-03-04T19:07:35.920 に答える
-2

それにかんする:

operator-> は実際には何も逆参照しないということですか?

いいえ、->型のオーバーロードの標準的な評価operator->は次のとおりです。

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

つまり、評価は再帰的に定義されます。ソース コードに が含まれている->場合、が適用され、それ自体が...を参照できるoperator->を持つ別の式が生成されます。->operator->

全体的な質問に関しては、ポインターが null の場合、動作は未定義であり、の欠如noexceptにより実装が可能になりますthrow。署名が であるnoexcept場合、実装はできませんでしたthrow(athrowは への呼び出しになりますstd::terminate)。

于 2013-03-04T14:47:16.573 に答える