34 votes

Pourquoi std :: unique_ptr operator * throw et operator-> does not throw?

Dans la norme C++ projet (N3485), il stipule ce qui suit:

20.7.1.2.4 unique_ptr observateurs [unique.ptr.unique.les observateurs]

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.

Vous pouvez voir qu' operator* (déréférencement) n'est pas spécifié comme noexcept, probablement parce qu'il peut provoquer une erreur de segmentation, mais alors operator-> sur le même objet est spécifié en tant que noexcept. Les exigences pour les deux sont les mêmes, il existe toutefois une différence dans la spécification d'exception.

J'ai remarqué qu'ils ont différents types de retour, on renvoie un pointeur et l'autre une référence. Est que le fait de dire qu' operator-> ne fait pas de déréférencement de quoi que ce soit?

Le fait de la question est que l'utilisation d' operator-> sur un pointeur de toute nature, qui est NULLE, sera l'erreur de segmentation (UB). Pourquoi, alors, est l'un de ces spécifié en tant que noexcept et l'autre pas?

Je suis sûr que j'ai oublié quelque chose.

EDIT:

En regardant std::shared_ptr nous avons ceci:

20.7.2.2.5 shared_ptr observateurs [util.smartptr.partagé.obs]

T& operator*() const noexcept;

T* operator->() const noexcept;

Ce n'est pas la même chose? N'ayant rien à voir avec l'appropriation différente de la sémantique?

26voto

Xeo Points 69818

Une erreur de segmentation est à l'extérieur de C++système d'exception. Si vous déréférencer un pointeur null, vous n'obtenez pas toute sorte d'exception (bien, au moins si vous vous conformez à l' Require: clause; voir ci-dessous pour plus de détails).

Pour operator->, il est généralement mis en œuvre comme simplement return m_ptr; (ou return get(); pour unique_ptr). Comme vous pouvez le voir, l'exploitant lui-même ne peut pas les jeter - elle retourne le pointeur. Pas de déréférencement, rien du tout. La langue a des règles spéciales pour l' p->identifier:

§13.5.6 [over.ref] p1

Une expression x->m est interprété comme (x.operator->())->m d'une classe d'objet x de type T si T::operator->() existe et si l'opérateur est sélectionné comme le meilleur match de la fonction par la surcharge de travail mécanisme de résolution (13.3).

Le ci-dessus s'applique de manière récursive et à la fin, doit céder le passage d'un pointeur, pour laquelle le haut- operator-> est utilisé. Cela permet aux utilisateurs de smart pointeurs et les itérateurs simplement pour ne smart->fun() sans se soucier de quoi que ce soit.

Une note pour l' Require: des parties de la description: Ces désignent des conditions préalables. Si vous ne respectez pas, vous êtes en invoquant UB.

Pourquoi, alors, est l'un de ces spécifié comme noexcept et l'autre pas?

Pour être honnête, je ne suis pas sûr. Il semblerait que le déréférencement d'un pointeur doit toujours être noexcept, cependant, unique_ptr permet de complètement changer ce que le pointeur interne est de type (par le biais de la deleter). Maintenant, en tant qu'utilisateur, vous pouvez définir entièrement différente de la sémantique pour l' operator* sur votre pointer type. Peut-être qu'il calcule les choses à la volée? Tout ce que la substance d'amusement, ce qui peut les jeter.


En regardant std::shared_ptr nous avons ceci:

C'est facile à expliquer - shared_ptr ne prend pas en charge le mentionné ci-dessus de personnalisation pour le type de pointeur, ce qui signifie que le haut-sémantique de toujours appliquer et *pp est T* n'est pas simplement jeter.

4voto

Jerry Coffin Points 237758

Pour ce que ça vaut, voici un peu de l'histoire, et la façon dont les choses se sont la façon dont ils sont maintenant.

Avant N3025, operator * n'a pas été spécifiée avec noexcept, mais sa description ne contiennent Throws: nothing. Cette exigence a été supprimée dans N3025:

Le changement [unique.ptr.unique.les observateurs] comme indiqué (834) [Pour plus de détails, consultez la section Remarques]:

typename add_lvalue_reference<T>::type operator*() const;
1 - Nécessite: get() !=0nullptr.
2 - Retour: *get().
3 - Lance: rien.

Voici le contenu de la section "Remarques" mentionné ci-dessus:

Pendant les examens de ce papier, il est devenu controversé comment spécifier la sémantique opérationnelle de l'opérateur*, l'opérateur[], et le caractère hétérogène des fonctions de comparaison. [structure.spécifications]/3 ne veut pas dire clairement si un Retourne l'élément (en l'absence de la nouvelle Équivalent à la formule) spécifie les effets. Plus sur il est difficile de savoir si cela pourrait permettre une telle expression de renvoi à la sortie via une exception, si en outre une Lance:-Rien d'élément (ce serait l'implémenteur être nécessaire pour attraper ceux-là?). Pour résoudre ce conflit, toutes les Jette élément a été supprimé pour ces opérations, qui est au moins compatible avec [unique.ptr.spécial] et d'autres parties de la norme. Le résultat de ceci est que nous donnons maintenant le soutien implicite pour potentiellement jeter les fonctions de comparaison, mais pas pour homogène == et !=, qui pourrait être un peu surprenant.

Le même document contient également une recommandation pour l'édition de la définition de l' operator ->, mais il se lit comme suit:

pointer operator->() const;
4 - Nécessite: get() !=0nullptr.
5 - Retour: get().
6 - les Lancers: rien.
7 - Remarque: l'utilisation nécessite généralement que T est un type complètes.

Autant que la question elle-même va: c'est une différence fondamentale entre l'opérateur lui-même, et l'expression dans laquelle l'opérateur est utilisé.

Lorsque vous utilisez operator*, l'opérateur déréférence le pointeur, qui peut jeter.

Lorsque vous utilisez operator->, l'opérateur lui-même, tout retourne un pointeur (qui n'est pas autorisé à les jeter). Que le pointeur est déréférencé dans l'expression qui contenait l' ->. Toute exception à partir de déréférencement du pointeur qui se passe dans les environs de l'expression plutôt que dans l'opérateur lui-même.

1voto

Sebastian Redl Points 18816

Franchement, cela ressemble à un défaut pour moi. Conceptuellement, a-> b devrait toujours être équivalent à (* a) .b, et cela s'applique même si a est un pointeur intelligent. Mais si * a n'est pas nul, alors (* a) .b ne l'est pas, et donc a-> b ne devrait pas l'être.

-2voto

Sujet:

Est que le fait de dire que l'opérateur-> ne fait pas de déréférencement de quoi que ce soit?

Non, la norme d'évaluation de -> , pour un type de surcharger operator-> est:

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

I. e. l'évaluation est définie de manière récursive, lorsque le code source contient un ->, operator-> est appliquée en produisant une autre expression avec un -> qui peut se référer à un operator->...

Concernant l'ensemble de la question, si le pointeur est null, le comportement est indéfini, et le manque d' noexcept permet une mise en œuvre à l' throw. Si la signature a été noexcept puis la mise en œuvre pourrait pas throw a throw serait un appel à l' std::terminate).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X