L’Incroyable Référence Const Qui N’Est pas Const
En travaillant sur la bibliothèque NamedType, je suis tombé sur une situation qui m’a laissé stupéfait: une référence const qui permet de modifier l’objet auquel elle fait référence. Sans const_cast
. Sans mutable
. Sans rien dans la manche.
Comment cela peut-il être? Et comment appliquer le const
dans cette référence const?
Une référence const qui n’est pas const
Voici une description d’une situation où cette référence étrange apparaît. Considérez la classe wrapper suivante:
template<typename T>class Wrapper{public: Wrapper(T const& value) : value_(value) {} T const& get() const { return value_; } private: T value_;};
Cette classe Wrapper
stocke une valeur de type T
et y donne accès via la méthode get()
(indépendamment de cela, si vous vous demandez si “get” est un bon nom, vous voudrez peut-être lire cette discussion détaillée). La partie importante est que la méthode get()
ne donne qu’un accès en lecture seule à la valeur stockée, car elle renvoie une référence const.
Pour vérifier cela, instancions le modèle avec int
, par exemple. La tentative de modification de la valeur stockée via la méthode get()
échoue à la compilation (essayez de cliquer sur le bouton Exécuter ci-dessous).
Ce qui est très bien, car nous voulons que get()
soit en lecture seule.
Mais instancions maintenant Wrapper
avec int&
. Un cas d’utilisation pour cela pourrait être d’éviter une copie, ou de garder une trace des modifications potentielles de a
par exemple. Essayons maintenant de modifier la valeur stockée via la référence const renvoyée par get()
:
Et si vous frappez la COURSE bout à bout, vous verrez que it ça compile! Et il imprime la valeur modifiée via la référence const, comme si rien de spécial ne s’était passé.
N’est-ce pas déconcertant? Ne ressentez-vous pas l’impression rassurante de sécurité que procure le const
qui s’effondre autour de nous ? À qui pouvons-nous faire confiance ?
Essayons de comprendre ce qui se passe avec cette référence.
Référence Const ou référence à const?
Le nœud du problème est que notre référence a est une référence const, mais pas une référence à const.
Pour comprendre ce que cela signifie, décomposons étape par étape ce qui se passe dans l’instanciation du modèle.
La méthode get()
renvoie un const T&
, avec T
venant de template T
. Dans notre second cas, T
vaut int&
, donc const T&
vaut const (int&) &
.
Regardons cela de plus près const (int&)
. int&
est une référence qui fait référence à un int
, et est autorisé à modifier ce int
. Mais const (int&)
est une référence int&
qui vaut const
, ce qui signifie que la référence elle-même ne peut pas être modifiée.
Que signifie modifier une référence, pour commencer? En théorie, cela signifierait le faire se référer à un autre objet, ou ne pas se référer à quoi que ce soit. Mais ces deux possibilités sont illégales en C++: une référence ne peut pas se relier. Il fait référence au même objet tout au long de sa vie.
Donc être const
ne dit pas grand-chose pour une référence, car ils sont toujours const
, car ils ne peuvent pas se relier. Cela implique que const (int&)
est effectivement du même type que int&
.
Ce qui nous laisse avec get()
renvoyant (int&) &
. Et selon les règles de l’effondrement des références, c’est int&
.
Donc get
renvoie un int&
. Son interface indique T const&
, mais c’est en fait un int&
. Un code pas vraiment expressif, n’est-ce pas ?
Comment faire de la référence const une référence à const
Maintenant que nous avons dépassé la première étape de compréhension du problème, comment pouvons-nous le résoudre? Autrement dit, comment faire en sorte que get()
renvoie une référence à la valeur stockée qui ne permet pas de la modifier?
Une façon de le faire est d’insérer explicitement un const
à l’intérieur de la référence. Nous pouvons le faire en supprimant la référence, en ajoutant un const
et en remettant la référence. Pour supprimer la référence, nous utilisons std::remove_reference
en C++11, ou la plus pratique std::remove_reference_t
en C++14:
Comme vous pouvez le voir si vous cliquez sur Exécuter, l’erreur de compilation attendue apparaît maintenant lorsque vous essayez de modifier la valeur stockée dans Wrapper
.
Je pense qu’il est utile de comprendre ce qui se passe avec cette référence, car c’est un code qui semble sûr, mais qui ne l’est vraiment pas, et c’est le genre le plus dangereux.
Vous pouvez jouer avec le code dans les terrains de jeux ci-dessus intégrés dans la page. Et un grand merci à l’utilisateur de débordement de pile rakete1111 pour m’avoir aidé à comprendre les raisons de ce problème.
Vous aimerez aussi
- L’analyse la plus vexante: Comment la Repérer et la Réparer rapidement