Norme C++

Correction Const

Qu’est-ce que la “correction const”?

Une bonne chose. Cela signifie utiliser le mot clé const pour empêcher les objets const de muter.

Par exemple, si vous vouliez créer une fonction f() qui acceptait un std::string, et que vous vouliez promettre aux appelants de ne pas modifier le std::string de l’appelant qui est passé à f(), vous pouvez demander à f() de recevoir son paramètre std::string

  • void f1(const std::string& s); // Passer par référence – à-const
  • void f2(const std::string* sptr); // Passer par le pointeur – vers-const
  • void f3(std::string s); // Pass by value

Dans les cas pass by reference-to-const et pass by pointer-to-const, toute tentative de modification de la std::string de l’appelant dans les fonctions f() serait signalée par le compilateur comme une erreur lors de la saisie de la valeur

dans les cas pass by reference-to-const et pass by pointer-to-const. temps de compilation. Cette vérification est entièrement effectuée au moment de la compilation: il n’y a pas d’espace d’exécution ou de coût de vitesse pour le const. Dans le cas de la valeur de passage (f3()), la fonction appelée obtient une copie de la valeur std::string de l’appelant. Cela signifie que f3() peut modifier sa copie locale, mais la copie est détruite lorsque f3() revient. En particulier, f3() ne peut pas modifier l’objet std::string de l’appelant.

À titre d’exemple opposé, supposons que vous vouliez créer une fonction g() qui acceptait un std::string, mais que vous souhaitiez faire savoir aux appelants que g() pourrait changer l’objet std::string de l’appelant. Dans ce cas, vous pouvez avoir g() recevoir son paramètre std::string

  • void g1(std::string& s); // Passer par référence-à-non-const
  • void g2(std::string* sptr); // Passer par pointeur vers non-const

L’absence de const dans ces fonctions indique au compilateur qu’ils sont autorisés à (mais ne sont pas obligés de) changer l’objet std::string du lanceur. Ainsi, ils peuvent passer leur std::string à l’une des f() fonctions, mais seul f3() (celui qui reçoit son paramètre “par valeur”) peut passer son std::string à g1() ou g2(). Si f1() ou f2() doivent appeler l’une ou l’autre fonction g(), une copie locale de l’objet std::string doit être transmise à la fonction g() ; le paramètre à f1() ou f2() ne peut pas être directement transmis à l’une ou l’autre fonction g(). Par ex..,

void g1(std::string& s);void f1(const std::string& s){ g1(s); // Compile-time Error since s is const std::string localCopy = s; g1(localCopy); // Okay since localCopy is not const}

Naturellement, dans le cas ci-dessus, toutes les modifications apportées par g1() sont apportées à l’objet localCopy qui est local à f1(). En particulier, aucune modification ne sera apportée au paramètre const qui a été passé par référence à f1().

Comment la “correction const” est-elle liée à la sécurité de type ordinaire?

Déclarer la valeur const d’un paramètre n’est qu’une autre forme de sécurité de type.

Si vous trouvez que la sécurité de type ordinaire vous aide à obtenir des systèmes corrects (c’est le cas, en particulier dans les grands systèmes), vous trouverez que const l’exactitude aide également.

L’avantage de la correction const est qu’elle vous empêche de modifier par inadvertance quelque chose que vous ne vous attendiez pas à modifier. Vous finissez par avoir besoin de décorer votre code avec quelques frappes supplémentaires (le mot clé const), avec l’avantage que vous dites au compilateur et aux autres programmeurs quelques informations sémantiques supplémentaires importantes — informations que le compilateur utilise pour éviter les erreurs et que d’autres programmeurs utilisent comme documentation.

Conceptuellement, vous pouvez imaginer que const std::string, par exemple, est une classe différente de la std::string ordinaire, car la variante const manque conceptuellement les différentes opérations mutatives disponibles dans la variante non const. Par exemple, vous pouvez imaginer conceptuellement qu’un const std::string n’a tout simplement pas d’opérateur d’affectation += ou d’autres opérations mutatives.

Dois-je essayer de corriger les choses “plus tôt” ou “plus tard”?

Au tout, très, tout début.

La correction arrière const entraîne un effet boule de neige: chaque const que vous ajoutez “ici” nécessite quatre autres ajouts “là-bas.”

Ajouter const tôt et souvent.

Que signifie “const X * p”?

Cela signifie que p pointe vers un objet de classe X, mais p ne peut pas être utilisé pour changer cet objet X (naturellement p pourrait également être NULL).

Lisez-le de droite à gauche: “p est un pointeur vers un X constant.”

Par exemple, si la classe X a une fonction membre const telle que inspect() const, il est correct de dire p->inspect(). Mais si la classe X a une fonction membre non const appelée mutate(), c’est une erreur si vous dites p->mutate().

De manière significative, cette erreur est détectée par le compilateur au moment de la compilation – aucun test d’exécution n’est effectué. Cela signifie que const ne ralentit pas votre programme et ne vous oblige pas à écrire des cas de test supplémentaires pour vérifier les choses au moment de l’exécution – le compilateur fait le travail au moment de la compilation.

Quelle est la différence entre “const X * p”, “X * const p” et “const X * const p”?

Lisez les déclarations du pointeur de droite à gauche.

  • const X* p signifie “p pointe vers un X qui est const” : l’objet X ne peut pas être modifié via p.
  • X* const p signifie “p est un pointeur const vers un pointeur X qui n’est pas const“: vous ne pouvez pas changer le pointeur p lui-même, mais vous pouvez changer l’objet X via p.
  • const X* const p signifie “p est un pointeur const vers un X qui est const“: vous ne pouvez pas changer le pointeur p lui-même, ni changer l’objet X via p.

Et, oh oui, ai-je mentionné de lire vos déclarations de pointeur de droite à gauche?

Que signifie “const X & x”?

Cela signifie que x alias un objet X, mais vous ne pouvez pas modifier cet objet X via x.

Lisez-le de droite à gauche: “x est une référence à un X qui est const.”

Par exemple, si la classe X a une fonction membre const telle que inspect() const, il est correct de dire x.inspect(). Mais si la classe X a une fonction membre non const appelée mutate(), c’est une erreur si vous dites x.mutate().

Ceci est entièrement symétrique avec les pointeurs vers const, y compris le fait que le compilateur effectue toutes les vérifications au moment de la compilation, ce qui signifie que const ne ralentit pas votre programme et ne vous oblige pas à écrire des cas de test supplémentaires pour vérifier les choses à l’exécution.

Que signifient “X const & x” et “X const * p”?

X const& x est équivalent à const X& x, et X const* x est équivalent à const X* x.

Certaines personnes préfèrent le style const à droite, l’appelant “cohérent const” ou, en utilisant un terme inventé par Simon Brand, “Est const.”En effet, le style “East const” peut être plus cohérent que l’alternative : le style “East const” place toujours le const à droite de ce qu’il constitue, tandis que l’autre style met parfois le const à gauche et parfois à droite (pour les déclarations de pointeur const et les fonctions membres const).

Avec le style “East const“, une variable locale const est définie avec le const à droite : int const a = 42;. De même, une variable static const est définie comme static double const x = 3.14;.Fondamentalement, chaque const se retrouve à droite de la chose qu’il constitue, y compris le const qui doit être à droite: const déclarations de pointeur et avec une fonction membre const.

Le style “East const” est également moins déroutant lorsqu’il est utilisé avec des alias de type: Pourquoi foo et bar ont-ils des types différents ici?

using X_ptr = X*;const X_ptr foo;const X* bar;

L’utilisation du style “East const” rend cela plus clair:

using X_ptr = X*;X_ptr const foo;X* const foobar;X const* bar;

Il est plus clair ici que foo et foobar sont du même type et que bar est un type différent.

Le style “Est const” est également plus cohérent avec les déclarations de pointeur. Contraster le style traditionnel:

const X** foo;const X* const* bar;const X* const* const baz;

avec le style “East const

X const** foo;X const* const* bar;X const* const* const baz;

Malgré ces avantages, le style const à droite n’est pas encore populaire, donc le code hérité a tendance à avoir le style traditionnel.

“X & const x” a-t-il un sens?

Non, c’est un non-sens.

Pour savoir ce que signifie la déclaration ci-dessus, lisez-la de droite à gauche: “x est une référence const à une référence X“. Mais c’est redondant — les références sont toujours const, dans le sens où vous ne pouvez jamais réinstaller areference pour le faire se référer à un objet différent. Jamais. Avec ou sans le const.

En d’autres termes, “X& const x” est fonctionnellement équivalent à “X& x“. Puisque vous ne gagnez rien en ajoutant le const après le &, vous ne devriez pas l’ajouter: cela confondra les gens — le const fera penser à certaines personnes quele X est const, comme si vous aviez dit “const X& x“.

Qu’est-ce qu’une “fonction membre const”?

Une fonction membre qui inspecte (plutôt que de muter) son objet.

Une fonction membre const est indiquée par un suffixe const juste après la liste des paramètres de la fonction membre. Les fonctions membres avec un suffixe const sont appelées “fonctions membres const” ou “inspecteurs.” Les fonctions membres sans suffixe const sont appelées ” fonctions membres non const” ou ” mutateurs.”

class Fred {public: void inspect() const; // This member promises NOT to change *this void mutate(); // This member function might change *this};void userCode(Fred& changeable, const Fred& unchangeable){ changeable.inspect(); // Okay: doesn't change a changeable object changeable.mutate(); // Okay: changes a changeable object unchangeable.inspect(); // Okay: doesn't change an unchangeable object unchangeable.mutate(); // ERROR: attempt to change unchangeable object}

La tentative d’appeler unchangeable.mutate() est une erreur détectée au moment de la compilation. Il n’y a pas d’espace d’exécution ou de speedpenalty pour const, et vous n’avez pas besoin d’écrire des cas de test pour le vérifier au moment de l’exécution.

La fonction membre const on inspect() de fin doit être utilisée pour signifier que la méthode ne changera pas l’état de l’objet (visible par le client). C’est légèrement différent de dire que la méthode ne changera pas les “bits bruts” de theobject’s struct. Les compilateurs C ++ ne sont pas autorisés à prendre l’interprétation “au niveau du bit” à moins qu’ils ne puissent résoudre le problème d’aliasing, qui ne peut normalement pas être résolu (c’est-à-dire qu’un alias nonconst pourrait exister qui pourrait modifier l’état de l’objet). Un autre aperçu (important) de ce problème d’alias: pointer vers un objet avec un pointeur vers const ne garantit pas que l’objet ne changera pas; il promet simplement que l’objet ne changera pas via ce pointeur.

Quelle est la relation entre un retour par référence et une fonction membre const ?

Si vous souhaitez renvoyer un membre de votre objet this par référence à partir d’une méthode inspector, vous devez le renvoyer en utilisant reference-to-const(const X& inspect() const) ou par valeur (X inspect() const).

class Person {public: const std::string& name_good() const; // Right: the caller can't change the Person's name std::string& name_evil() const; // Wrong: the caller can change the Person's name int age() const; // Also right: the caller can't change the Person's age // ...};void myCode(const Person& p) // myCode() promises not to change the Person object...{ p.name_evil() = "Igor"; // But myCode() changed it anyway!!}

La bonne nouvelle est que le compilateur vous attrapera souvent si vous vous trompez. En particulier, si vous retournez accidentellement un membre de votre objet this par une référence non const, comme dans Person::name_evil() ci-dessus, le compilateur le détectera souvent et vous donnera une erreur de compilation lors de la compilation des entrailles de, dans ce cas, Person::name_evil().

La mauvaise nouvelle est que le compilateur ne vous attrapera pas toujours: il y a des cas où le compilateur ne vous donnera tout simplement pas de message d’erreur au moment de la compilation.

Traduction : il faut réfléchir. Si cela vous fait peur, trouvez une autre ligne de travail; “penser” n’est pas un mot de quatre lettres.

Rappelez-vous la “philosophie const” répandue dans cette section: une fonction membre const ne doit pas changer (ou permettre à un appelant de changer) l’état logique de l’objet this (ALIAS état abstrait ALIAS état sens). Pensez à ce que signifie un objet, pas à la façon dont il est implémenté en interne. L’âge et le nom d’une personne sont logiquementpartie de la Personne, mais le voisin et l’employeur de la Personne ne le sont pas. Une méthode inspector qui renvoie une partie de l’état logique/abstrait/signifiant de l’objet this ne doit pas renvoyer un pointeur (ou une référence) non const à cette partie, indépendamment du fait que cette partie soit implémentée en interne en tant que membre de données direct physiquement intégré dans l’objet this ou d’une autre manière.

Quel est le problème avec la “surcharge de const”?

const la surcharge vous aide à obtenir const exactitude.

const la surcharge est lorsque vous avez une méthode d’inspecteur et une méthode de mutateur avec le même nom et le même nombre et types de paramètres. Les deux méthodes distinctes ne diffèrent que par le fait que l’inspecteur est const et que le mutateur n’est pas const.

L’utilisation la plus courante de la surcharge const est avec l’opérateur indice. Vous devriez généralement essayer d’utiliser l’un des templates de conteneur standard, tels que std::vector, mais si vous devez créer votre propre classe qui a un opérateur d’abonnement, voici la règle empirique: les opérateurs d’indice viennent souvent par paires.

class Fred { /*...*/ };class MyFredList {public: const Fred& operator (unsigned index) const; // Subscript operators often come in pairs Fred& operator (unsigned index); // Subscript operators often come in pairs // ...};

L’opérateur d’indice const renvoie une référence const, de sorte que le compilateur empêchera les appelants de modifier / modifier par inadvertance la Fred. L’opérateur d’indice nonconst renvoie une référence nonconst, qui est votre façon d’indiquer à vos appelants (et au compilateur) que vos appelants sont autorisés à modifier l’objet Fred.

Lorsqu’un utilisateur de votre classe MyFredList appelle l’opérateur d’indice, le compilateur sélectionne la surcharge à appeler en fonction de la constance de leur MyFredList. Si l’appelant a un MyFredList a ou MyFredList& a, alors a appellera l’opérateur d’indice non const, et l’appelant se retrouvera avec une référence non const à un Fred:

Par exemple, supposons que class Fred ait une méthode d’inspecteur inspect() const et une méthode de mutateur mutate():

void f(MyFredList& a) // The MyFredList is non-const{ // Okay to call methods that inspect (look but not mutate/change) the Fred at a: Fred x = a; // Doesn't change to the Fred at a: merely makes a copy of that Fred a.inspect(); // Doesn't change to the Fred at a: inspect() const is an inspector-method // Okay to call methods that DO change the Fred at a: Fred y; a = y; // Changes the Fred at a a.mutate(); // Changes the Fred at a: mutate() is a mutator-method}

Cependant, si l’appelant a un const MyFredList a ou const MyFredList& a, alors a appellera l’opérateur d’abonnement const, et l’appelant se retrouvera avec une référence const à un Fred. Cela permet à l’appelant d’inspecter le Fred à a, mais cela empêche l’appelant de muter / changer par inadvertance le Fred à a.

void f(const MyFredList& a) // The MyFredList is const{ // Okay to call methods that DON'T change the Fred at a: Fred x = a; a.inspect(); // Compile-time error (fortunately!) if you try to mutate/change the Fred at a: Fred y; a = y; // Fortunately(!) the compiler catches this error at compile-time a.mutate(); // Fortunately(!) the compiler catches this error at compile-time}

La surcharge Const pour les opérateurs d’indice et de fonction est illustrée ici, ici, ici, ici et ici.

Vous pouvez bien sûr également utiliser const – surcharge pour des choses autres que l’opérateur d’indice.

Comment cela peut-il m’aider à concevoir de meilleures classes si je distingue l’état logique de l’état physique?

Parce que cela vous encourage à concevoir vos classes de l’extérieur vers l’intérieur plutôt que de l’intérieur vers l’extérieur, ce qui rend vos classes et vos objets plus faciles à comprendre et à utiliser, plus intuitifs, moins sujets aux erreurs et plus rapides. (D’accord, c’est une légère simplification excessive. Pour comprendre tous les if et les but, il vous suffit de lire le reste de cette réponse!)

Comprenons cela de l’intérieur vers l’extérieur — vous allez (devriez) concevoir vos classes de l’extérieur vers l’intérieur, mais si vous êtes nouveau dans ce concept, il est plus facile de comprendre de l’intérieur vers l’extérieur.

À l’intérieur, vos objets ont un état physique (ou concret ou bit à bit). C’est l’état qui est facile à voir et à comprendre pour les programmeurs ; c’est l’état qui serait là si la classe n’était qu’un style C struct.

À l’extérieur, vos objets ont des utilisateurs de votre classe, et ces utilisateurs sont limités à utiliser uniquement des fonctions membres public et des fonctions friend. Ces utilisateurs externes perçoivent également l’objet comme ayant un état, par exemple, si theobject est de classe Rectangle avec les méthodes width(), height() et area(), vos utilisateurs diraient que ces trois éléments font tous partie de l’état logique (ou abstrait ou signifiant) de l’objet. Pour un utilisateur externe, l’objet Rectangle a en réalité une zone, même si cette zone est calculée à la volée (par exemple, si la méthode area() renvoie le produit de la largeur et de la hauteur de l’objet). En fait, et c’est le point important, vos utilisateurs ne savent pas et ne se soucient pas de savoir comment vous implémentez l’une de ces méthodes; vos utilisateurs perçoivent toujours, de leur point de vue, que votre objet a logiquement un état de largeur, de hauteur et d’aire.

L’exemple area() montre un cas où l’état logique peut contenir des éléments qui ne sont pas directement réalisés dans l’état physique. Le contraire est également vrai: les classes cachent parfois intentionnellement une partie de l’état physique (concret, bit à bit) de leurs objets aux utilisateurs — elles ne fournissent intentionnellement aucune fonction membre public ou friend qui permettrait aux utilisateurs de lire ou d’écrire ou même de connaître cet état caché. Cela signifie qu’il y abits dans l’état physique de l’objet qui n’ont pas d’éléments correspondants dans l’état logique de l’objet.

À titre d’exemple de ce dernier cas, un objet de collection peut mettre en cache sa dernière recherche dans l’espoir d’améliorer les performances de sa prochaine recherche. Ce cache fait certainement partie de l’état physique de l’objet, mais il s’agit là d’un détail de mise en œuvre interne qui ne sera probablement pas exposé aux utilisateurs — il ne fera probablement pas partie de l’état logique de l’objet. Dire ce qui est facile si vous pensez de l’extérieur – de l’intérieur: si les utilisateurs de l’objet de collection doivent maintenant vérifier l’état du cache lui-même, le cache est transparent et ne fait pas partie de l’état logique de l’objet.

La constance de mes fonctions membres publiques doit-elle être basée sur ce que la méthode apporte à l’état logique ou physique de l’objet?

Logique.

Il n’y a aucun moyen de rendre cette partie suivante facile. Ça va faire mal. La meilleure recommandation est de s’asseoir. Et pour votre sécurité, assurez-vous qu’il n’y a pas d’outils tranchants à proximité.

Revenons à l’exemple de collection-objet. Rappeler: il existe une méthode de recherche qui enregistre la dernière recherche dans l’espoir d’accélérer les recherches futures.

Énonçons ce qui est probablement évident: supposons que la méthode de recherche n’apporte aucune modification à l’état logique de l’objet de collection.

Alors the le moment est venu de vous blesser. Prêts?

Voici: si la méthode de recherche n’apporte aucune modification à l’état logique de l’objet de collection, mais qu’elle modifie l’état physique de l’objet de collection (elle modifie le cache très réel), la méthode de lecture devrait-elle être const?

La réponse est un oui retentissant. (Il y a des exceptions à chaque règle, donc “Oui” devrait vraiment avoir un astérisque à côté, mais la grande majorité du temps, la réponse est Oui.)

Il s’agit de “logique const” plutôt que de “physique const.”Cela signifie que la décision de décorer amethod avec const devrait dépendre principalement de la question de savoir si cette méthode laisse l’état logique inchangé, indépendamment (êtes-vous assis?) (vous voudrez peut-être vous asseoir) indépendamment du fait que la méthode apporte des modifications très réelles à l’état physique très réel de l’objet.

Au cas où cela ne tomberait pas, ou au cas où vous n’auriez pas encore mal, séparons-le en deux cas:

  • Si une méthode change une partie de l’état logique de l’objet, elle est logiquement un mutateur ; elle ne devrait pas être const evenif (comme cela se produit réellement!) la méthode ne change aucun bit physique de l’état concret de l’objet.
  • Inversement, une méthode est logiquement un inspecteur et devrait être const si elle ne change jamais une partie de l’état logique de l’objet, même si (comme cela se produit réellement!) la méthode modifie les bits physiques de l’état concret de l’objet.

Si vous êtes confus, relisez-le.

Si vous n’êtes pas confus mais que vous êtes en colère, tant mieux: vous ne l’aimerez peut-être pas encore, mais au moins vous le comprenez. Respirez profondément et répétez après moi: “La constness d’une méthode devrait avoir du sens de l’extérieur de l’objet.”

Si vous êtes toujours en colère, répétez ceci trois fois : “La constance d’une méthode doit avoir un sens pour les utilisateurs de l’objet, et ces utilisateurs ne peuvent voir que l’état logique de l’objet.”

Si vous êtes toujours en colère, désolé, c’est ce que c’est. Sucez-le et vivez avec. Oui, il y aura des exceptions; chaque règle les a. Mais en règle générale, dans l’ensemble, cette notion logique const est bonne pour vous et bonne pour votre logiciel.

Encore une chose. Cela va devenir inique, mais soyons précis quant à savoir si une méthode modifie l’état logique de l’objet. Si vous êtes en dehors de la classe — vous êtes un utilisateur normal, chaque expérience que vous pourriez effectuer (chaque méthode ou séquence de méthodes que vous appelez) aurait les mêmes résultats (mêmes valeurs de retour, mêmes exceptions ou absence d’exceptions), que vous ayez d’abord appelé cette méthode de recherche. Si la fonction de recherche a modifié tout comportement futur de toute méthode future (non seulement en la rendant plus rapide, mais en modifiant le résultat, en modifiant la valeur de retour, en modifiant l’exception), la méthode de recherche a changé l’état logique de l’objet — c’est un mutuateur. Mais si la méthode de recherche n’a rien changé d’autre que peut-être accélérer certaines choses, alors c’est un inspecteur.

Que dois-je faire si je veux qu’une fonction de membre const apporte une modification “invisible” à un membre de données ?

Utilisez mutable (ou, en dernier recours, utilisez const_cast).

Un petit pourcentage d’inspecteurs doivent apporter des modifications à l’état physique d’un objet qui ne peuvent pas être observées par les utilisateurs externes — des modifications à l’état physique mais pas logique.

Par exemple, l’objet de collection discuté précédemment a mis en cache sa dernière recherche dans l’espoir d’améliorer les performances de sa prochaine recherche. Étant donné que le cache, dans cet exemple, ne peut être observé directement par aucune partie de l’interface publique de l’objet de collection (autre que la synchronisation), son existence et son état ne font pas partie de l’état logique de l’objet, de sorte que les modifications apportées à celui-ci sont invisibles pour les utilisateurs externes. La méthode de recherche est un inspecteur car elle ne change jamais l’état logique de l’objet, indépendamment du fait que, au moins pour l’implémentation actuelle, elle change l’état physique de l’objet.

Lorsque les méthodes changent l’état physique mais pas logique, la méthode doit généralement être marquée comme const car elle est vraiment une méthode d’inspecteur. Cela crée un problème: lorsque le compilateur voit votre méthode const changer l’état physique de l’objet this, il se plaindra — cela donnera à votre code un message d’erreur.

Le langage du compilateur C++ utilise le mot-clé mutable pour vous aider à adopter cette notion logique const. Dans ce cas, vous marqueriez le cache avec le mot-clé mutable, de cette façon, le compilateur sait qu’il est autorisé à changer dans une méthode const ou via tout autre pointeur ou référence const. Dans notre jargon, le mot clé mutable marque les portions de l’état physique de l’objet qui ne font pas partie de l’état logique.

Le mot clé mutable va juste avant la déclaration du membre de données, c’est-à-dire au même endroit où vous pourriez mettre const. L’autre approche, non préférée, consiste à rejeter la ness const du pointeur this, probablement via le mot-clé const_cast:

Set* self = const_cast<Set*>(this); // See the NOTE below before doing this!

Après cette ligne, self aura les mêmes bits que this, c’est-à-dire self == this, mais self est un Set* plutôt qu’un const Set* (techniquement this est un const Set* const, mais le const le plus à droite n’est pas pertinent pour cette discussion).Cela signifie que vous pouvez utiliser self pour modifier l’objet pointé par this.

REMARQUE: une erreur extrêmement improbable peut se produire avec const_cast. Cela n’arrive que lorsque trois choses très rares sont combinées en même temps: un membre de données qui devrait être mutable (comme indiqué ci-dessus), un compilerthat qui ne prend pas en charge le mot-clé mutable et / ou un programmeur qui ne l’utilise pas, et un objet qui a été initialement défini comme étant const (par opposition à un objet normal, non const pointé par un pointeur vers-const).Bien que cette combinaison soit si rare qu’elle puisse ne jamais vous arriver, si cela se produisait, le code pourrait ne pas fonctionner (la norme indique que le comportement n’est pas défini).

Si jamais vous souhaitez utiliser const_cast, utilisez mutable à la place. En d’autres termes, si jamais vous devez changer un membre d’unobject et que cet objet est pointé par un pointeur vers const, la chose la plus sûre et la plus simple à faire est d’ajouter mutable à la déclaration du membre. Vous pouvez utiliser const_cast si vous êtes sûr que l’objet réel n’est pas const (par exemple, si vous êtes sûr que l’objet est déclaré quelque chose comme ceci: Set s;), mais si l’objet lui-même peut être const (par exemple, si il peut être déclaré comme: const Set s;), utilisez mutable plutôt que const_cast.

Veuillez ne pas écrire en disant que la version X du compilateur Y sur la machine Z vous permet de modifier un membre non mutable d’un objet const. Je m’en fiche — c’est illégal selon la langue et votre code échouera probablement sur un compilateur différent ou même une version différente (une mise à niveau) du même compilateur. Dis juste non. Utilisez mutable à la place. Écrire du code qui est garanti pour fonctionner, pas du code qui ne semble pas se casser.

const_cast signifie-t-il des opportunités d’optimisation perdues?

En théorie, oui; en pratique, non.

Même si le langage interdisait const_cast, la seule façon d’éviter de vider le cache du registre à travers un appel de fonction de membre const serait de résoudre le problème d’alias (c’est-à-dire, pour prouver qu’il n’y a pas de pointeurs non const pointant vers l’objet). Cela ne peut se produire que dans de rares cas (lorsque l’objet est construit dans la portée de l’invocation de fonction membre const, et lorsque toutes les invocations de fonction membre non const entre la construction de l’objet et l’invocation de fonction membre const sont liées statiquement, et lorsque chacune de ces invocations est également inline d, et lorsque le constructeur lui-même est inline d, et lorsqu’une fonction membre que le constructeur appelle est inline).

Pourquoi le compilateur me permet-il de changer un int après l’avoir pointé avec un const int *?

Parce que “const int* p” signifie “p promet de ne pas changer le *p, “pas” *p promet de ne pas changer.”

Faire pointer un const int* vers un int ne const – ify le int. Le int ne peut pas être modifié via le const int*, mais si quelqu’un d’autre a un int* (remarque: non const) qui pointe vers (“alias”) le même int, alors ce int* peut être utilisé pour changer le int. Exemple:

void f(const int* p1, int* p2){ int i = *p1; // Get the (original) value of *p1 *p2 = 7; // If p1 == p2, this will also change *p1 int j = *p1; // Get the (possibly new) value of *p1 if (i != j) { std::cout << "*p1 changed, but it didn't change via pointer p1!\n"; assert(p1 == p2); // This is the only way *p1 could be different }}int main(){ int x = 5; f(&x, &x); // This is perfectly legal (and even moral!) // ...}

Notez que main() et f(const int*,int*) peuvent être dans différentes unités de compilation compilées à différents jours de la semaine. Dans ce cas, le compilateur ne peut pas détecter l’alias au moment de la compilation. Par conséquent, il n’y a aucun moyen de faire une règle linguistique qui interdit ce genre de chose. En fait, nous ne voudrions même pas faire une telle chose, car en général, il est considéré comme une fonctionnalité que vous pouvez avoir de nombreux pointeurs pointant vers la même chose. Le fait qu’un de ces pointeurs promet de ne pas changer la “chose” sous-jacente n’est qu’une promesse faite par le pointeur; ce n’est pas une promesse faite par la “chose”.

“const Fred *p” signifie-t-il que *p ne peut pas changer?

Non! (Ceci est lié à la FAQ sur l’alias des pointeurs int.)

const Fred* p” signifie que le Fred ne peut pas être modifié via le pointeur p, mais il peut y avoir d’autres façons d’accéder à theobject sans passer par un const (comme un pointeur non const alias tel qu’un Fred*). Par exemple, si vous avez deux pointeurs “const Fred* p” et “Fred* q” qui pointent vers le même objet Fred (alias), le pointeur q peut être utilisé pour changer l’objet Fred mais le pointeur p ne le peut pas.

class Fred {public: void inspect() const; // A const member function void mutate(); // A non-const member function};int main(){ Fred f; const Fred* p = &f; Fred* q = &f; p->inspect(); // Okay: No change to *p p->mutate(); // Error: Can't change *p via p q->inspect(); // Okay: q is allowed to inspect the object q->mutate(); // Okay: q is allowed to mutate the object f.inspect(); // Okay: f is allowed to inspect the object f.mutate(); // Okay: f is allowed to mutate the object // ...}

Pourquoi ai-je une erreur lors de la conversion d’un Foo ** → const Foo **?

Car la conversion de Foo**const Foo** serait invalide et dangereuse.

C++ permet la conversion (sûre) Foo*Foo const*, mais donne une erreur si vous essayez de convertir implicitement Foo**const Foo**.

La raison pour laquelle cette erreur est une bonne chose est donnée ci-dessous. Mais d’abord, voici la solution la plus courante: changez simplement const Foo** en const Foo* const*:

class Foo { /* ... */ };void f(const Foo** p);void g(const Foo* const* p);int main(){ Foo** p = /*...*/; // ... f(p); // ERROR: it's illegal and immoral to convert Foo** to const Foo** g(p); // Okay: it's legal and moral to convert Foo** to const Foo* const* // ...}

La raison pour laquelle la conversion de Foo**const Foo** est dangereuse est qu’elle vous permettrait de modifier silencieusement et accidentellement un objet const Foo sans fonte:

class Foo {public: void modify(); // make some modification to the this object};int main(){ const Foo x; Foo* p; const Foo** q = &p; // q now points to p; this is (fortunately!) an error *q = &x; // p now points to x p->modify(); // Ouch: modifies a const Foo!! // ...}

Si la ligne q = &p était légale, q pointerait sur p. La ligne suivante, *q = &x, change p elle-même (puisque *q vaut p) pour pointer sur x. Ce serait une mauvaise chose, car nous aurions perdu le qualificatif const: p est un Foo* mais x est un const Foo. La ligne p->modify() exploite la capacité de p à modifier son référent, ce qui est le vrai problème, puisque nous avons fini par modifier un const Foo.

Par analogie, si vous cachez un criminel sous un déguisement légal, il peut alors exploiter la confiance accordée à ce déguisement.C’est mauvais.

Heureusement, C++ vous empêche de le faire: la ligne q = &p est signalée par le compilateur C++ comme une erreur de compilation. Rappel: veuillez ne pas pointer votre chemin autour de ce message d’erreur de compilation. Dites Simplement Non!

(Remarque: il existe une similitude conceptuelle entre cela et l’interdiction de convertir Derived** en Base**.)

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.