C++ Core Guidelines a été présenté récemment (félicitations!) Et je suis préoccupé par gsl::not_null
type. Comme indiqué dans I.12: Déclarez un pointeur qui ne doit pas être nul comme not_null
:
Pour éviter de déréférencer les erreurs nullptr. Pour améliorer les performances en évitant les contrôles redondants pour nullptr.
...
En indiquant l'intention dans la source, les implémenteurs et les outils peuvent fournir de meilleurs diagnostics, tels que la recherche de certaines classes d'erreurs grâce à l'analyse statique, et effectuer des optimisations, telles que la suppression des branches et des tests nuls.
L'intention est claire. Cependant, nous avons déjà une fonction de langue pour cela. Les pointeurs qui ne peuvent pas être nuls sont appelés références. Et bien que les références ne puissent pas être rebondies une fois qu'elles sont créées, ce problème est résolu par std::reference_wrapper
.
La principale différence entre gsl::not_null
et std::reference_wrapper
Je vois que ce dernier ne peut être utilisé qu'à la place des pointeurs, tandis que le premier fonctionne sur n'importe quoi nullptr
- assignable (citation de F.17: Utilisez un not_null pour indiquer que "null" n'est pas une valeur valide ):
not_null
n'est pas réservé aux pointeurs intégrés. Cela fonctionne pourarray_view
,string_view
,unique_ptr
,shared_ptr
et d'autres types de pointeurs.
J'imagine le tableau de comparaison des fonctionnalités comme suit:
T&
:
nullptr
? - Oui std::reference_wrapper<T>
:
nullptr
? - Oui gsl::not_null<T*>
:
nullptr
? - Oui Voici enfin les questions:
std::reference_wrapper
est maintenant inutile? [~ # ~] ps [~ # ~] J'ai créé des balises cpp-core-guidelines
et guideline-support-library
pour cela, j'espère bien.
Les références sont pas des pointeurs qui ne peuvent pas être nuls. Les références sont sémantiquement très différentes des pointeurs.
Les références ont valeur sémantique d'affectation et de comparaison; c'est-à-dire que les opérations d'affectation ou de comparaison impliquant des références lisent et écrivent la référence valeur. Les pointeurs ont (contre-intuitivement) référence la sémantique d'affectation et de comparaison; c'est-à-dire que les opérations d'affectation ou de comparaison impliquant des pointeurs lisent et écrivent le référence lui-même (c'est-à-dire l'adresse de l'objet référencé).
Comme vous l'avez noté, les références ne peuvent pas être rebondies (en raison de leur sémantique d'attribution de valeur), mais le reference_wrapper<T>
modèle de classe peut être rebond, car il a référence sémantique d'affectation. Ceci est dû au fait reference_wrapper<T>
est conçu pour être utilisé avec des conteneurs et des algorithmes STL et ne se comporterait pas correctement si son opérateur d'affectation de copie ne faisait pas la même chose que son constructeur de copie. Pourtant, reference_wrapper<T>
a toujours une valeur comparaison sémantique, comme une référence, donc il se comporte très différemment des pointeurs lorsqu'il est utilisé avec des conteneurs et des algorithmes STL. Par exemple, set<T*>
peut contenir des pointeurs vers différents objets avec la même valeur, tandis que set<reference_wrapper<T>>
peut contenir une référence à un seul objet avec une valeur donnée.
Le not_null<T*>
le modèle de classe a une affectation de référence et sémantique de comparaison, comme un pointeur; c'est un type de pointeur. Cela signifie qu'il se comporte comme un pointeur lorsqu'il est utilisé avec des conteneurs et des algorithmes STL. Il ne peut tout simplement pas être nul.
Donc, vous avez raison dans votre évaluation, sauf que vous avez oublié la sémantique de comparaison. Et non, reference_wrapper<T>
ne sera pas rendu obsolète par tout type de type pointeur, car il possède une sémantique de comparaison de valeurs de type référence.
Je pense qu'il existe encore des cas d'utilisation pour std::reference_wrapper
qui ne sont pas couverts par gsl::not_null
. Fondamentalement, std::reference_wrapper
reflète une référence et a un operator T&
conversion, tandis que not_null
a une interface de pointeur avec operator->
. Un cas d'utilisation qui me vient immédiatement à l'esprit est lors de la création d'un fil:
void funcWithReference(int& x) { x = 42; }
int i=0;
auto t = std::thread( funcWithReference, std::ref(i) );
Si je n'ai aucun contrôle sur funcWithReference
, je ne peux pas utiliser not_null
.
La même chose s'applique aux foncteurs pour les algorithmes, et j'ai dû l'utiliser pour la liaison boost::signals
aussi.