Quel est l'intérêt des pointeurs const?


149

Je ne parle pas de pointeurs vers des valeurs const, mais de pointeurs const eux-mêmes.

J'apprends le C et le C ++ au-delà des choses très basiques et jusqu'à aujourd'hui, j'ai réalisé que les pointeurs sont passés par valeur aux fonctions, ce qui a du sens. Cela signifie qu'à l'intérieur d'une fonction, je peux faire pointer le pointeur copié vers une autre valeur sans affecter le pointeur d'origine de l'appelant.

Alors, quel est l'intérêt d'avoir un en-tête de fonction qui dit:

void foo(int* const ptr);

À l'intérieur d'une telle fonction, vous ne pouvez pas faire pointer ptr sur autre chose car c'est const et vous ne voulez pas qu'il soit modifié, mais une fonction comme celle-ci:

void foo(int* ptr);

Ça marche aussi bien! car le pointeur est copié de toute façon et le pointeur de l'appelant n'est pas affecté même si vous modifiez la copie. Alors, quel est l'avantage de const?


29
Que faire si vous voulez garantir, au moment de la compilation, que le pointeur ne peut pas et ne doit pas être modifié pour pointer vers autre chose?
Platinum Azure

25
Exactement le même point que n'importe quel constparamètre.
David Heffernan

2
@PlatinumAzure, j'ai quelques années d'expérience en programmation, pourtant mes pratiques logicielles font défaut. Je suis content d'avoir posé cette question car je n'ai jamais ressenti le besoin de faire signaler au compilateur mes propres erreurs de logique. Ma réaction initiale au moment où j'ai soumis la question était "pourquoi devrais-je me soucier si le pointeur est modifié?, Cela n'affectera pas l'appelant". Jusqu'à ce que je lis toutes les réponses, j'ai compris que je devrais m'en soucier parce que si j'essaie de le modifier, ma logique de codage et moi avons tort (ou il y avait une faute de frappe comme manquer l'astérisque) et je n'aurais peut-être pas réalisé si le compilateur ne l'avait pas fait ' t dites-moi.
R. Ruiz.

3
@ R.Ruiz. Sans aucun doute, même les plus expérimentés d'entre nous pourraient bénéficier de garanties supplémentaires d'exactitude. Parce que l'ingénierie logicielle est une forme d'ingénierie où un peu plus de marge de manœuvre peut être acceptable (HTTP 502 aléatoires, connexions lentes, l'image occasionnelle qui ne se charge pas ne sont pas des occasions extraordinaires, mais une panne de moteur dans un avion est inacceptable et probablement grave), parfois les gens programment avec une hâte excessive. Le même argument qui justifie l'écriture de tests unitaires expliquera l'utilisation des constgaranties -correctness. Cela nous permet simplement de nous sentir plus sûrs que notre code est indubitablement correct.
Platinum Azure du

Réponses:


207

const est un outil que vous devriez utiliser dans la poursuite d'un concept C ++ très important:

Trouvez les bogues au moment de la compilation, plutôt qu'au moment de l'exécution, en demandant au compilateur d'appliquer ce que vous voulez dire.

Même si cela ne change pas la fonctionnalité, l'ajout constgénère une erreur de compilation lorsque vous faites des choses que vous ne vouliez pas faire. Imaginez la faute de frappe suivante:

void foo(int* ptr)
{
    ptr = 0;// oops, I meant *ptr = 0
}

Si vous utilisez int* const, cela générerait une erreur du compilateur car vous modifiez la valeur en ptr. L'ajout de restrictions via la syntaxe est une bonne chose en général. N'allez pas trop loin - l'exemple que vous avez donné est un cas où la plupart des gens ne prennent pas la peine de l'utiliser const.


8
Merci, c'est une réponse qui me convainc. Vous mettez const pour que le compilateur vous avertisse de vos propres erreurs d'affectation. Votre exemple est parfait pour illustrer ce concept car c'est une erreur courante avec les pointeurs. Que toi!
R. Ruiz.

25
"Aidez le compilateur à vous aider" est le mantra que je chante normalement pour cela.
Flexo

1
+1, mais voici un cas intéressant où il est discutable: stackoverflow.com/questions/6305906
...

3
c'est donc la même réponse que vous donneriez à "Pourquoi rendre les variables privées, alors que vous ne pouvez tout simplement pas les utiliser en dehors de la classe."
Lee Louviere

cela peut être un peu hors sujet, mais où se trouve ce pointeur const dans la mémoire? Je sais que tous les const se trouvent dans la partie globale de la mémoire, mais je ne suis pas sûr à 100% d'un const que vous êtes passé en tant que var func. merci et désolé si ce n'est pas le sujet
Tomer

77

Je tiens à n'utiliser que const arguments car cela permet plus de vérifications du compilateur: si je réassigne accidentellement une valeur d'argument à l'intérieur de la fonction, le compilateur me mord.

Je réutilise rarement des variables, il est plus propre de créer de nouvelles variables pour contenir de nouvelles valeurs, donc essentiellement toutes mes déclarations de variables le sont const(sauf dans certains cas tels que les variables de boucle oùconst cela empêcherait le code de fonctionner).

Notez que cela n'a de sens que dans la définition d'une fonction. Il n'appartient pas à la déclaration , ce que voit l'utilisateur. Et l'utilisateur ne se soucie pas de savoir si j'utiliseconst pour les paramètres à l'intérieur de la fonction.

Exemple:

// foo.h
int frob(int x);
// foo.cpp
int frob(int const x) {
   MyConfigType const config = get_the_config();
   return x * config.scaling;
}

Remarquez comment sont à la fois l'argument et la variable locale const. Ni l'un ni l'autre n'est nécessaire, mais avec des fonctions encore légèrement plus grandes, cela m'a évité à plusieurs reprises de faire des erreurs.


17
+1d'un autre constfanatique obsédé. Cependant, je préfère que mes compilateurs m'aboient dessus. Je fais trop d'erreurs et j'en souffrirais gravement s'ils mordent.
sbi

2
+1, je recommande fortement la conststratégie " sauf s'il y a une bonne raison". Il y a cependant quelques bonnes exceptions, par exemple Copie et échange
Flexo

2
Si je concevais un nouveau langage, les objets déclarés seraient en lecture seule ("const") par défaut. Vous auriez besoin d'une syntaxe spéciale, peut-être un mot-clé "var", pour rendre un objet accessible en écriture.
Keith Thompson

@Keith Je suis tenté de dire «bien sûr». Tout le reste est stupide à ce stade. Mais malheureusement, la plupart des concepteurs de langage semblent être en désaccord avec nous… En fait, je l'ai déjà posté (dans une question maintenant supprimée, «Quelle est votre opinion de programmation la plus controversée?»).
Konrad Rudolph

1
@KubaOber Je ne vois pas du tout en quoi c'est une pessimisation. Si vous constatez par la suite que vous avez besoin de modifier ce qui a été passé dans le corps de la fonction, supprimez simplement constl'argument de l'argument et, de préférence, commentez pourquoi. Ce petit peu de travail supplémentaire ne justifie pas de marquer tous les arguments comme non constpar défaut et de vous ouvrir à toutes les erreurs potentielles que cela crée.
underscore_d

20

Votre question touche à quelque chose de plus général: les arguments de fonction doivent-ils être const?

La constance des arguments de valeur (comme votre pointeur) est un détail d'implémentation et ne fait pas partie de la déclaration de fonction. Cela signifie que votre fonction est toujours la suivante:

void foo(T);

Il appartient entièrement à l' implémenteur de la fonction si elle souhaite utiliser la variable d'argument de la portée des fonctions de manière mutable ou constante:

// implementation 1
void foo(T const x)
{
  // I won't touch x
  T y = x;
  // ...
}

// implementation 2
void foo(T x)
{
  // l33t coding skillz
  while (*x-- = zap()) { /* ... */ }
}

Alors, suivez la règle simple de ne jamais mettre constdans la déclaration (en-tête), et mettez-la dans la définition (implémentation) si vous ne voulez ou n'avez pas besoin de modifier la variable.


5
Je suis d'accord avec cela - mais je suis quelque peu mal à l'aise avec l'idée de rendre la déclaration et la définition différentes. Pour les choses autres que const, la déclaration et la (partie prototype de) la définition vont généralement être identiques.
Keith Thompson

@KeithThompson: Eh bien, vous n'êtes pas obligé de le faire si vous ne le voulez pas. Ne mettez pas constdans la déclaration. C'est entièrement à vous si vous souhaitez ajouter le qualificatif dans l'implémentation.
Kerrek SB

1
Comme je l'ai dit, je suis d'accord pour dire que mettre constla déclaration mais pas la définition a du sens. C'est juste un problème dans la langue que c'est le seul cas où rendre la déclaration et la définition non identiques a du sens. (Ce n'est pas le seul problème de C, bien sûr.)
Keith Thompson

16

Le qualificatif const de niveau supérieur est ignoré dans les déclarations, de sorte que les déclarations de la question déclarent exactement la même fonction. D'autre part, dans la définition (implémentation), le compilateur vérifiera que si vous marquez le pointeur comme const, il n'est pas modifié à l'intérieur du corps de la fonction.


Je ne savais pas ça. Donc, si j'essaye de surcharger void foo (int * const ptr) avec void foo (int * t ptr) j'obtiendrai une erreur de compilation. Merci!
R. Ruiz.

@ R.Ruiz. Si votre décélération est void foo (int * t ptr) et que votre définition est void foo (int * const ptr), vous n'obtiendrez PAS d'erreur de compilation.
Trevor Hickey

1
@TrevorHickey Ils le feront ... mais pas pour la raison qu'ils pensent. int* t ptrest une erreur de syntaxe. Sans cela, les deux sont identiques pour des raisons de surcharge.
underscore_d

14

Vous avez raison, pour l'appelant, cela ne fait absolument aucune différence. Mais pour l'auteur de la fonction, cela peut être un filet de sécurité "d'accord, je dois m'assurer de ne pas faire ce point sur la mauvaise chose". Pas très utile mais pas inutile non plus.

C'est fondamentalement la même chose que d'avoir un int const the_answer = 42dans votre programme.


1
Qu'importe où ils le pointent, c'est un pointeur alloué sur la pile? La fonction ne peut pas gâcher cela. Il est beaucoup plus judicieux d'utiliser des pointeurs en lecture seule lorsqu'il s'agit de pointeurs à pointeurs. Ce n'est pas la même chose que int const! Je vais voter contre cela juste pour cette déclaration trompeuse. const intet int constsont équivalents, tandis que const int*et int* constont deux significations différentes!
Lundin

1
@lundin Supposons que la fonction soit volumineuse et contienne d'autres éléments. On peut accidentellement le faire pointer vers autre chose (sur la pile de la fonction). Ce n'est pas un problème pour l'appelant mais cela pourrait certainement l'être pour l'appelé. Je ne vois rien de trompeur dans mes déclarations: " pour l'auteur de la fonction, cela peut être un filet de sécurité".
cnicutar

1
@Lundin À propos de la int constpartie; Je mets le type avant const (cela ne semble pas naturel) depuis un certain temps et je suis conscient des différences. Cet article pourrait s'avérer utile. J'ai moi-même eu des raisons légèrement différentes de passer à ce style cependant.
cnicutar

1
D'accord. Je viens d'écrire une longue réponse de ma part au cas où quelqu'un d'autre que vous et moi lirions cet article. J'ai également inclus une justification pour laquelle int const est un mauvais style et const int est un bon style.
Lundin

Lundin, je lis aussi! Je suis d'accord que const int est le meilleur style. @cnicutar, votre première réponse à Lundin est ce que je recherchais lorsque j'ai soumis cette question. Donc, le problème n'est pas dans l'appelant (comme je le pensais sans réfléchir), mais const est une assurance pour l'appelé. J'aime cette idée, merci.
R. Ruiz.

14

Il y a beaucoup de constmots - clés, c'est plutôt complexe. En général, ajouter beaucoup de const à votre programme est considéré comme une bonne pratique de programmation, recherchez sur le Web "const correctness" et vous trouverez de nombreuses informations à ce sujet.

Le mot clé const est un soi-disant "qualificatif de type", les autres sont volatileet restrict. Au moins volatile suit les mêmes règles (déroutantes) que const.


Tout d'abord, le mot-clé const a deux objectifs. Le plus évident est de protéger les données (et les pointeurs) contre une utilisation abusive intentionnelle ou accidentelle en les rendant en lecture seule. Toute tentative de modification d'une variable const sera repérée par le compilateur au moment de la compilation.

Mais il y a aussi un autre but dans tout système avec une mémoire en lecture seule, à savoir de s'assurer qu'une certaine variable est allouée à l'intérieur d'une telle mémoire - cela pourrait être EEPROM ou flash par exemple. Celles-ci sont connues sous le nom de mémoires non volatiles, NVM. Une variable allouée dans NVM suivra bien entendu toutes les règles d'une variable const.

Il existe plusieurs façons d'utiliser le constmot - clé:

Déclarez une variable constante.

Cela peut être fait soit comme

const int X=1; or
int const X=1;

Ces deux formes sont tout à fait équivalentes . Ce dernier style est considéré comme un mauvais style et ne doit pas être utilisé.

La raison pour laquelle la deuxième ligne est considérée comme un mauvais style est probablement parce que les "spécificateurs de classe de stockage" tels que static et extern peuvent également être déclarés après le type réel, int staticetc. Mais le faire pour les spécificateurs de classe de stockage est étiqueté comme une fonctionnalité obsolète par le comité C (ISO 9899 N1539 draft, 6.11.5). Par conséquent, pour des raisons de cohérence, il ne faut pas non plus écrire de qualificatifs de type de cette manière. Cela ne sert à rien d'autre que de confondre le lecteur de toute façon.

Déclarez un pointeur vers une variable constante.

const int* ptr = &X;

Cela signifie que le contenu de «X» ne peut pas être modifié. C'est la manière normale de déclarer des pointeurs comme celui-ci, principalement dans le cadre des paramètres de fonction pour la "correction de const". Parce que 'X' n'a pas besoin d'être déclaré comme const, il peut s'agir de n'importe quelle variable. En d'autres termes, vous pouvez toujours «mettre à niveau» une variable vers const. Techniquement, C permet également de passer de const à une simple variable par des typecasts explicites, mais cela est considéré comme une mauvaise programmation et les compilateurs donnent généralement des avertissements contre cela.

Déclarer un pointeur constant

int* const ptr = &X;

Cela signifie que le pointeur lui - même est constant. Vous pouvez modifier ce sur quoi il pointe, mais vous ne pouvez pas modifier le pointeur lui-même. Cela n'a pas beaucoup d'utilisations, il y en a quelques-unes, comme s'assurer qu'un pointeur pointé sur (pointeur vers pointeur) ne voit pas son adresse modifiée lorsqu'il est passé en paramètre à une fonction. Vous devrez écrire quelque chose de pas trop lisible comme ceci:

void func (int*const* ptrptr)

Je doute que de nombreux programmeurs C puissent obtenir le const et * directement là-dedans. Je sais que je ne peux pas - j'ai dû vérifier avec GCC. Je pense que c'est pourquoi vous voyez rarement cette syntaxe pour le pointeur à pointeur, même si elle est considérée comme une bonne pratique de programmation.

Les pointeurs de constantes peuvent également être utilisés pour garantir que la variable de pointeur elle-même est déclarée dans la mémoire en lecture seule, par exemple, vous pouvez déclarer une sorte de table de recherche basée sur des pointeurs et l'allouer dans NVM.

Et bien sûr, comme indiqué par d'autres réponses, des pointeurs constants peuvent également être utilisés pour imposer la "correction de const".

Déclarer un pointeur constant vers des données constantes

const int* const ptr=&X;

Ce sont les deux types de pointeurs décrits ci-dessus combinés, avec tous les attributs des deux.

Déclarer une fonction membre en lecture seule (C ++)

Puisqu'il s'agit de C ++, je dois également mentionner que vous pouvez déclarer les fonctions membres d'une classe comme const. Cela signifie que la fonction n'est pas autorisée à modifier un autre membre de la classe lorsqu'elle est appelée, ce qui à la fois empêche le programmeur de la classe d'erreurs accidentelles mais informe également l'appelant de la fonction membre qu'il ne dérangera rien en l'appelant. La syntaxe est:

void MyClass::func (void) const;

8

... aujourd'hui, j'ai réalisé que les pointeurs sont passés par valeur aux fonctions, ce qui a du sens.

(imo) cela n'a vraiment pas de sens par défaut. la valeur par défaut la plus judicieuse est de passer comme pointeur non réassignable ( int* const arg). autrement dit, j'aurais préféré que les pointeurs passés comme arguments soient déclarés implicitement const.

Alors, quel est l'avantage de const?

l'avantage est que c'est assez facile et parfois peu clair lorsque vous modifiez l'adresse vers laquelle pointe l'argument, de sorte que vous pouvez introduire un bogue quand il n'est pas const assez facilement. modifier l'adresse est atypique. il est plus clair de créer une variable locale si votre intention est de modifier l'adresse. De plus, la manipulation de pointeurs bruts est un moyen facile d'introduire des bogues.

il est donc plus clair de passer par une adresse immuable et de créer une copie (dans ces cas atypiques) lorsque vous souhaitez modifier l'adresse vers laquelle pointe l'argument:

void func(int* const arg) {
    int* a(arg);
    ...
    *a++ = value;
}

ajoutant que local est pratiquement gratuit et réduit les risques d'erreurs, tout en améliorant la lisibilité.

à un niveau supérieur: si vous manipulez l'argument en tant que tableau, il est généralement plus clair et moins sujet aux erreurs pour le client de déclarer l'argument en tant que conteneur / collection.

en général, l'ajout de const aux valeurs, arguments et adresses est une bonne idée car vous ne réalisez pas toujours les effets secondaires, que le compilateur applique volontiers. par conséquent, il est aussi utile que const comme utilisé dans plusieurs autres cas (par exemple, la question est similaire à «Pourquoi devrais-je déclarer des valeurs const?»). heureusement, nous avons aussi des références, qui ne peuvent pas être réaffectées.


4
+1 pour cela étant la valeur par défaut. C ++, comme la plupart des langages, a le mauvais sens. Au lieu d'avoir un constmot - clé, il devrait avoir un mutablemot - clé (enfin, il a, mais avec la mauvaise sémantique).
Konrad Rudolph

2
Idée intéressante ayant const par défaut. Merci pour votre réponse!
R. Ruiz.

6

Si vous effectuez une programmation de systèmes embarqués ou de pilotes de périphériques où vous avez des périphériques mappés en mémoire, les deux formes de `` const '' sont souvent utilisées, l'une pour empêcher le pointeur d'être réaffecté (car il pointe vers une adresse matérielle fixe.) Et, si le périphérique le registre vers lequel il pointe est un registre matériel en lecture seule, puis un autre const détectera beaucoup d'erreurs au moment de la compilation plutôt qu'à l'exécution.

Un registre de puce périphérique 16 bits en lecture seule peut ressembler à ceci:

static const unsigned short *const peripheral = (unsigned short *)0xfe0000UL;

Ensuite, vous pouvez facilement lire le registre du matériel sans avoir à recourir au langage d'assemblage:

input_word = *peripheral;


5

int iVal = 10; int * const ipPtr = & iVal;

Tout comme une variable const normale, un pointeur const doit être initialisé à une valeur lors de la déclaration, et sa valeur ne peut pas être modifiée.

Cela signifie qu'un pointeur const pointera toujours sur la même valeur. Dans le cas ci-dessus, ipPtr pointera toujours vers l'adresse d'iVal. Cependant, comme la valeur pointée est toujours non-const, il est possible de modifier la valeur pointée via le déréférencement du pointeur:

* ipPtr = 6; // autorisé, puisque pnPtr pointe vers un int non-const


5

La même question peut être posée pour tout autre type (pas seulement pour les pointeurs):

/* Why is n const? */
const char *expand(const int n) {
    if (n == 1) return "one";
    if (n == 2) return "two";
    if (n == 3) return "three";
    return "many";
}

LOL, tu as raison. Le cas des pointeurs m'est venu à l'esprit en premier parce que je pensais qu'ils étaient spéciaux, mais ils sont comme n'importe quelle autre variable passée par valeur.
R. Ruiz.

5

Votre question porte davantage sur la raison pour laquelle définir une variable en tant que paramètre de pointeur const et pas seulement vers une fonction. Les mêmes règles s'appliquent ici que lorsque vous définissez une variable comme constante, s'il s'agit d'un paramètre de fonction ou de variable membre ou d'une variable locale.

Dans votre cas particulier, cela ne fait pas de différence sur le plan fonctionnel comme dans de nombreux autres cas lorsque vous déclarez une variable locale comme const mais cela met une restriction que vous ne pouvez pas modifier cette variable.


votre commentaire fait comprendre combien ma question est futile parce que vous avez raison: c'est la même chose que d'avoir n'importe quel autre paramètre comme const. Cela peut sembler inutile mais il est là pour aider le programmeur à avoir cette restriction et éviter les bogues
R. Ruiz.

4

Passer un pointeur const à une fonction n'a pas de sens, car il sera de toute façon passé par valeur. C'est juste une de ces choses qui sont autorisées par la conception générale du langage. L'interdire simplement parce que cela n'a pas de sens ne ferait que créer la spécification du langage. plus grand.

Si vous êtes à l'intérieur d'une fonction, c'est bien sûr un autre cas. Avoir un pointeur qui ne peut pas changer ce sur quoi il pointe est une assertion qui rend le code plus clair.


4

Je suppose qu'un avantage serait que le compilateur peut effectuer des optimisations plus agressives à l'intérieur de la fonction en sachant que ce pointeur ne peut pas changer.

Il évite également par exemple. passer ce pointeur à une sous-fonction qui accepte une référence de pointeur non-const (et pourrait donc changer le pointeur comme void f(int *&p)), mais je suis d'accord, que l'utilité est quelque peu limitée dans ce cas.


+1 pour les optimisations. Au moins les livres disent que c'est vrai. Ce que je voudrais, c'est comprendre exactement ce que sont ces optimisations
R. Ruiz.

4

Un exemple où un pointeur const est hautement applicable peut être démontré ainsi. Considérez que vous avez une classe avec un tableau dynamique à l'intérieur, et que vous souhaitez transmettre l'accès utilisateur au tableau, mais sans leur accorder les droits de modifier le pointeur. Considérer:

#include <new>
#include <string.h>

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const GetArray(){ return Array; }
};

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' '; //You can still modify the chars in the array, user has access
    Temp.GetArray()[1] = ' '; 
    printf("%s\n",Temp.GetArray());
}

Ce qui produit:

Données d'entrée
mettre des données

Mais si nous essayons ceci:

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' ';
    Temp.GetArray()[1] = ' ';
    printf("%s\n",Temp.GetArray());
    Temp.GetArray() = NULL; //Bwuahahahaa attempt to set it to null
}

On a:

erreur: lvalue requise comme opérande gauche de l'affectation // Drat déjoué à nouveau!

Il est donc clair que nous pouvons modifier le contenu du tableau, mais pas le pointeur du tableau. Bon si vous voulez vous assurer que le pointeur a un état cohérent lors du renvoi à l'utilisateur. Il y a cependant un hic:

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' ';
    Temp.GetArray()[1] = ' ';
    printf("%s\n",Temp.GetArray());
    delete [] Temp.GetArray(); //Bwuahaha this actually works!
}

Nous pouvons toujours supprimer la référence mémoire du pointeur, même si nous ne pouvons pas modifier le pointeur lui-même.

Donc, si vous voulez que la référence mémoire pointe toujours vers quelque chose (IE ne doit jamais être modifié, de la même manière qu'une référence fonctionne actuellement), alors c'est hautement applicable. Si vous voulez que l'utilisateur ait un accès complet et le modifie, alors non-const est pour vous.

Éditer:

Après avoir noté le commentaire okorz001 de ne pas pouvoir attribuer en raison de GetArray () étant un opérande de bonne valeur, son commentaire est tout à fait correct, mais ce qui précède s'applique toujours si vous deviez renvoyer une référence au pointeur (je suppose que j'ai supposé que GetArray était référant une référence), par exemple:

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const &GetArray(){ return Array; } //Note & reference operator
        char * &GetNonConstArray(){ return Array; } //Note non-const
};

int main()
{
    TestA Temp;
    Temp.GetArray() = NULL; //Returns error
    Temp.GetNonConstArray() = NULL; //Returns no error
}

Reviendra dans le premier entraînant une erreur:

erreur: affectation de l'emplacement en lecture seule 'Temp.TestA :: GetArray ()'

Mais le second se produira joyeusement malgré les conséquences potentielles sur le dessous.

Évidemment, la question se posera «pourquoi voudriez-vous renvoyer une référence à un pointeur»? Il existe de rares cas où vous devez attribuer de la mémoire (ou des données) directement au pointeur d'origine en question (par exemple, créer votre propre front-end malloc / free ou new / free), mais dans ces cas, il s'agit d'une référence non-const . Une référence à un pointeur const Je n'ai pas rencontré de situation qui le justifierait (à moins que peut-être en tant que variables de référence const déclarées plutôt que de types de retour?).

Considérez si nous avons une fonction qui prend un pointeur const (par opposition à un qui ne le fait pas):

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const &GetArray(){ return Array; }

        void ModifyArrayConst(char * const Data)
        {
            Data[1]; //This is okay, this refers to Data[1]
            Data--; //Produces an error. Don't want to Decrement that.
            printf("Const: %c\n",Data[1]);
        }

        void ModifyArrayNonConst(char * Data)
        {
            Data--; //Argh noo what are you doing?!
            Data[1]; //This is actually the same as 'Data[0]' because it's relative to Data's position
            printf("NonConst: %c\n",Data[1]);
        }
};

int main()
{
    TestA Temp;
    Temp.ModifyArrayNonConst("ABCD");
    Temp.ModifyArrayConst("ABCD");
}

L'erreur dans le const produit ainsi le message:

erreur: décrémentation du paramètre en lecture seule 'Data'

Ce qui est bien car nous ne voulons probablement pas faire cela, à moins que nous ne voulions causer les problèmes indiqués dans les commentaires. Si nous éditons le décrément dans la fonction const, ce qui suit se produit:

NonConst: A
Const: B

Clairement, même si A est «Data [1]», il est traité comme «Data [0]» car le pointeur NonConst a permis l'opération de décrémentation. Avec le const implémenté, comme une autre personne l'écrit, nous détectons le bogue potentiel avant qu'il ne se produise.

Une autre considération principale, est qu'un pointeur const peut être utilisé comme une pseudo référence, en ce que la chose vers laquelle la référence pointe ne peut pas être modifiée (on se demande si c'est peut-être ainsi qu'elle a été implémentée). Considérer:

int main()
{
    int A = 10;
    int * const B = &A;
    *B = 20; //This is permitted
    printf("%d\n",A);
    B = NULL; //This produces an error
}

Lors de la tentative de compilation, génère l'erreur suivante:

erreur: affectation de la variable en lecture seule 'B'

Ce qui est probablement une mauvaise chose si une référence constante à A était souhaitée. Si B = NULLest commenté, le compilateur se fera un plaisir de nous laisser modifier *Bet donc A. Cela peut ne pas sembler utile avec ints, mais considérez si vous aviez une seule position d'une application graphique où vous vouliez un pointeur non modifiable qui y faisait référence que vous pourriez passer autour.

Son utilisation est variable (excusez le jeu de mots involontaire), mais utilisé correctement, c'est un autre outil dans la boîte pour aider à la programmation.


2
Temp.GetArray() = NULLéchoue parce que Temp.GetArray()c'est une rvalue, pas parce qu'elle est qualifiée const. De plus, je crois que le qualificatif const est supprimé de tous les types de retour.
Oscar Korz

@ okorz001: Après avoir testé, vous avez effectivement raison. Cependant, ce qui précède s'applique si vous renvoyez une référence au pointeur lui-même. Je modifierai mon message en conséquence.
SSight3

3

Il n'y a rien de spécial à propos des pointeurs où vous ne voudriez jamais qu'ils soient const. Tout comme vous pouvez avoir des intvaleurs constantes de membre de classe , vous pouvez également avoir des pointeurs constants pour des raisons similaires: vous voulez vous assurer que personne ne change jamais ce qui est pointé. Les références C ++ abordent quelque peu cela, mais le comportement du pointeur est hérité de C.


3

Je pense que cela empêcherait le code d'incrémenter ou de décrémenter le pointeur dans le corps de la fonction.


3

Types de déclaration de toutes les variables comme-
(1) Déclaration d'une variable constante.
DataType const varibleName;

 int const x;
    x=4; //you can assign its value only One time
(2) Déclarer un pointeur vers une variable constante
const dataType* PointerVaribleName=&X;
 const int* ptr = &X;
     //Here pointer variable refer contents of 'X' that is const Such that its cannot be modified
dataType* const PointerVaribleName=&X;
 int* const ptr = &X;
     //Here pointer variable itself is constant  Such that value of 'X'  can be modified But pointer can't be modified


Nous fournissons une variable de pointeur en tant que const dans une fonction en tant qu'argument lorsque nous ne voulons pas changer sa valeur réelle
Pyadav
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.