Article de reference

const (programmation informatique)

Dans certains langages de programmation , const est un qualificateur de type (un mot-clé appliqué à un type de données ) qui indique que les données sont en lecture seule. Bien ...

Dans certains langages de programmation , const est un qualificateur de type (un mot-clé appliqué à un type de données ) qui indique que les données sont en lecture seule. Bien que cela puisse être utilisé pour déclarer des constantes , const dans la famille de langages C diffère des constructions similaires dans d'autres langages en ce qu'il fait partie du type et a donc un comportement compliqué lorsqu'il est combiné avec des pointeurs , des références, des types de données composites et la vérification de type . Dans d'autres langages, les données ne se trouvent pas dans un seul emplacement mémoire , mais sont copiées au moment de la compilation pour chaque utilisation. Les langages qui l'utilisent incluent C , C++ , D , JavaScript , Julia et Rust .

Introduction

Appliqué dans une déclaration d'objet , indique que l'objet est une constante : sa valeur ne peut pas être modifiée, contrairement à une variable . Cette utilisation de base – déclarer des constantes – a des parallèles dans de nombreux autres langages.

Cependant, contrairement à d'autres langages, dans la famille de langages C, le constfait partie du type et non de l' objet . Par exemple, en C, déclare un objet de type – le fait partie du type, comme s'il était analysé « (int const) x » – alors qu'en Ada , déclare une constante (une sorte d'objet) de type : le fait partie de l' objet , mais ne fait pas partie du type . intconstx=1;xint constconstX : constant INTEGER := 1_XINTEGERconstant

Cela a deux conséquences subtiles. Tout d'abord, constil peut être appliqué à des parties d'un type plus complexe, par exemple, int const * const x;il déclare un pointeur constant vers un entier constant, tandis qu'il int const * x;déclare un pointeur variable vers un entier constant et int * const x;qu'il déclare un pointeur constant vers un entier variable. Deuxièmement, comme constil fait partie du type, il doit correspondre dans le cadre de la vérification de type. Par exemple, le code suivant n'est pas valide :

void f ( int & x ); // ... int const i ; f ( i );

car l'argument de fdoit être un entier variablei , mais est un entier constant . Cette correspondance est une forme de correction du programme , et est connue sous le nom de const-correctness . Cela permet une forme de programmation par contrat , où les fonctions spécifient dans le cadre de leur signature de type si elles modifient ou non leurs arguments, et si leur valeur de retour est modifiable ou non. Cette vérification de type est principalement intéressante pour les pointeurs et les références – pas pour les types de valeur de base comme les entiers – mais aussi pour les types de données composites ou les types basés sur des modèles tels que les conteneurs . Elle est masquée par le fait que le constpeut souvent être omis, en raison de la coercition de type ( conversion de type implicite ) et du fait que C est un appel par valeur (C++ et D sont soit des appels par valeur, soit des appels par référence).

Conséquences

L'idée de constance n'implique pas que la variable telle qu'elle est stockée dans la mémoire de l'ordinateur soit non inscriptible. En fait, const-ness est une construction de compilation qui indique ce qu'un programmeur doit faire, pas nécessairement ce qu'il peut faire. Notez cependant que dans le cas de données prédéfinies (telles que char const * les littéraux de chaîne ), C constest souvent non inscriptible.

Distinction des constantes

Bien qu'une constante ne change pas sa valeur pendant l'exécution du programme, un objet déclaré constpeut en effet changer sa valeur pendant l'exécution du programme. Un exemple courant est celui des registres en lecture seule dans les systèmes embarqués, comme l'état actuel d'une entrée numérique. Les registres de données pour les entrées numériques sont souvent déclarés comme constet volatile. Le contenu de ces registres peut changer sans que le programme ne fasse quoi que ce soit ( volatile), mais il serait mal avisé pour le programme de tenter d'y écrire ( const).

Autres utilisations

De plus, une fonction membre (non statique) peut être déclarée comme const. Dans ce cas, le thispointeur à l'intérieur d'une telle fonction est de type object_type const *plutôt que simplement de type object_type *. Cela signifie que les fonctions non constantes pour cet objet ne peuvent pas être appelées depuis l'intérieur d'une telle fonction, ni que les variables membres ne peuvent être modifiées. En C++, une variable membre peut être déclarée comme mutable, indiquant que cette restriction ne s'applique pas à elle. Dans certains cas, cela peut être utile, par exemple avec la mise en cache , le comptage de références et la synchronisation des données . Dans ces cas, la signification logique (état) de l'objet reste inchangée, mais l'objet n'est pas physiquement constant puisque sa représentation au niveau du bit peut changer.

Syntaxe

En C, C++ et D, tous les types de données, y compris ceux définis par l'utilisateur, peuvent être déclarés const, et la cohérence impose que toutes les variables ou tous les objets soient déclarés comme tels, à moins qu'ils ne doivent être modifiés. Une telle utilisation proactive de constrend les valeurs « plus faciles à comprendre, à suivre et à raisonner », et augmente ainsi la lisibilité et la compréhensibilité du code et simplifie le travail en équipe et la maintenance du code, car il communique des informations sur l'utilisation prévue d'une valeur. Cela peut aider le compilateur ainsi que le développeur lors du raisonnement sur le code. Cela peut également permettre à un compilateur optimisant de générer un code plus efficace.

Types de données simples

Pour les types de données simples non pointeurs, l'application du constqualificateur est simple. Il peut être placé de chaque côté de certains types pour des raisons historiques (par exemple, const char foo = 'a';est équivalent à char const foo = 'a';). Sur certaines implémentations, l'utilisation constde deux fois (par exemple, const char constou char const const) génère un avertissement mais pas une erreur.

Pointeurs et références

Pour les types pointeur et référence, la signification de constest plus compliquée : soit le pointeur lui-même, soit la valeur pointée, soit les deux, peuvent être const. De plus, la syntaxe peut être déroutante. Un pointeur peut être déclaré comme un constpointeur vers une valeur accessible en écriture, ou un pointeur accessible en écriture vers une constvaleur, ou constun pointeur vers constune valeur.Un constpointeur ne peut pas être réaffecté pour pointer vers un objet différent de celui qui lui est initialement attribué, mais il peut être utilisé pour modifier la valeur vers laquelle il pointe (appelée le pointeur ). Les variables de référence en C++ sont une syntaxe alternative pour constles pointeurs. Un pointeur vers un constobjet, en revanche, peut être réaffecté pour pointer vers un autre emplacement mémoire (qui doit être un objet du même type ou d'un type convertible), mais il ne peut pas être utilisé pour modifier la mémoire vers laquelle il pointe. Un constpointeur vers un constobjet peut également être déclaré et ne peut ni être utilisé pour modifier le pointeur ni être réaffecté pour pointer vers un autre objet. Le code suivant illustre ces subtilités :

void Foo ( int * ptr , int const * ptrToConst , int * const constPtr , int const * const constPtrToConst ) { * ptr = 0 ; // OK : modifie les données pointées ptr = NULL ; // OK : modifie le pointeur
* ptrToConst = 0 ; // Erreur ! Impossible de modifier les données pointées ptrToConst = NULL ; // OK : modifie le pointeur
* constPtr = 0 ; // OK : modifie les données pointées constPtr = NULL ; // Erreur ! Impossible de modifier le pointeur
* constPtrToConst = 0 ; // Erreur ! Impossible de modifier les données pointées constPtrToConst = NULL ; // Erreur ! Impossible de modifier le pointeur }

Convention C

Suivant la convention C habituelle pour les déclarations, déclaration suit utilisation, et le *dans un pointeur est écrit sur le pointeur, indiquant le déréférencement . Par exemple, dans la déclaration int *ptr, la forme déréférencée *ptrest un int, tandis que la forme de référence ptrest un pointeur vers un int. Ainsi constmodifie le nom à sa droite. La convention C++ est plutôt d'associer le *au type, comme dans int* ptr, et de lire le constcomme modifiant le type vers la gauche. int const * ptrToConstpeut ainsi être lu comme " *ptrToConstest un int const" (la valeur est constante), ou " ptrToConstest un int const *" (le pointeur est un pointeur vers un entier constant). Ainsi :

int * ptr ; // *ptr est une valeur int int const * ptrToConst ; // *ptrToConst est une constante (int : valeur entière) int * const constPtr ; // constPtr est une constante (int * : pointeur entier) int const * const constPtrToConst ; // constPtrToConst est un pointeur constant et pointe // vers une valeur constante

Convention C++

Conformément à la convention C++ d'analyse du type et non de la valeur, une règle empirique consiste à lire la déclaration de droite à gauche. Ainsi, tout ce qui se trouve à gauche de l'étoile peut être identifié comme le type pointé et tout ce qui se trouve à droite de l'étoile correspond aux propriétés du pointeur. Par exemple, dans notre exemple ci-dessus, int const *peut être lu comme un pointeur accessible en écriture qui fait référence à un entier non accessible en écriture, et int * constpeut être lu comme un pointeur non accessible en écriture qui fait référence à un entier accessible en écriture.

Une règle plus générique qui vous aide à comprendre les déclarations et définitions complexes fonctionne comme ceci :

  1. trouver l'identifiant dont vous souhaitez comprendre la déclaration
  2. lire le plus loin possible vers la droite (c'est-à-dire jusqu'à la fin de la déclaration ou jusqu'à la parenthèse fermante suivante, selon la première éventualité)
  3. revenez à l'endroit où vous avez commencé et lisez en arrière vers la gauche (c'est-à-dire jusqu'au début de la déclaration ou jusqu'à la parenthèse ouvrante correspondant à la parenthèse fermante trouvée à l'étape précédente)
  4. lorsque vous avez atteint le début de la déclaration, vous avez terminé. Sinon, continuez à l'étape 2, au-delà de la parenthèse fermante qui a été mise en correspondance en dernier.

Voici un exemple :

Lors de la lecture vers la gauche, il est important de lire les éléments de droite à gauche. Ainsi, an int const *devient un pointeur vers un int const et non un pointeur const vers un int .

Dans certains cas, C/C++ permet constde placer le mot-clé à gauche du type. Voici quelques exemples :

const int * ptrToConst ; //identique à : int const *ptrToConst, const int * const constPtrToConst ; //identique à : int const *const constPtrToConst

Bien que C/C++ autorise de telles définitions (qui correspondent étroitement à la langue anglaise lors de la lecture des définitions de gauche à droite), le compilateur lit toujours les définitions selon la procédure mentionnée ci-dessus : de droite à gauche. Mais mettre const avant ce qui doit être constant introduit rapidement des incohérences entre ce que vous avez l'intention d'écrire et ce que le compilateur décide que vous avez écrit. Considérez les pointeurs vers des pointeurs :

int ** ptr ; // un pointeur vers un pointeur vers des ints int const ** ptr // un pointeur vers un pointeur vers une valeur int constante // (pas un pointeur vers un pointeur constant vers des ints) int * const * ptr // un pointeur vers un pointeur const vers des valeurs int // (pas un pointeur constant vers un pointeur vers des ints) int ** const ptr // un pointeur constant vers des pointeurs vers des ints // (ptr, l'identifiant, étant const n'a aucun sens) int const ** const ptr // un pointeur constant vers des pointeurs vers des valeurs int constantes

Remarque finale concernant les définitions de pointeurs : écrivez toujours le symbole du pointeur (le *) autant que possible à droite. Associer le symbole du pointeur au type est délicat, car cela suggère fortement un type de pointeur, ce qui n'est pas le cas. Voici quelques exemples :

int * a ; /* write: */ int * a ; // a est un pointeur vers un int int * a , b ; // CONFUS /* write: */ int * a , b ; // a est un pointeur vers un int, // mais b est un simple int int * a , * b ; // MOCHE : a et b sont tous deux des pointeurs vers des ints /* write: */ int * a , * b ;

La FAQ de Bjarne Stroustrup recommande de ne déclarer qu'une seule variable par ligne si vous utilisez la convention C++, pour éviter ce problème.

Les mêmes considérations s'appliquent à la définition des références et des références rvalue :

int var = 22 ; int const & refToConst = var ; // OK int const & ref2 = var , ref3 = var ; // CONFUS : // ref2 est une référence, mais pas ref3 : // ref3 est une constante int initialisée avec // la valeur de var int & const constRef = var ; // ERREUR : car les références ne peuvent pas changer de toute façon.
// C++ : 
int && rref = int ( 5 ), value = 10 ; // CONFUS : // rref est une référence rvalue, mais value est // un simple int. /* écriture : */ int && rref = int ( 5 ), value = 10 ;

Des déclarations plus complexes sont rencontrées lors de l'utilisation de tableaux multidimensionnels et de références (ou pointeurs) vers des pointeurs. Bien qu'il soit parfois avancé que de telles déclarations sont source de confusion et d'erreurs et qu'elles doivent donc être évitées ou remplacées par des structures de niveau supérieur, la procédure décrite au début de cette section peut toujours être utilisée sans introduire d'ambiguïtés ou de confusion.

Paramètres et variables

constpeut être déclaré à la fois sur des paramètres de fonction et sur des variables ( statiques ou automatiques, y compris globales ou locales). L'interprétation varie selon les utilisations. Une constvariable statique (variable globale ou variable locale statique) est une constante et peut être utilisée pour des données telles que des constantes mathématiques, telles que double const PI = 3.14159– des paramètres réalistement plus longs, ou des paramètres de compilation globaux. Une constvariable automatique (variable locale non statique) signifie qu'une seule affectation se produit, bien qu'une valeur différente puisse être utilisée à chaque fois, comme int const x_squared = x * x. Un constparamètre en passage par référence signifie que la valeur référencée n'est pas modifiée – elle fait partie du contrat – tandis qu'un constparamètre en passage par valeur (ou le pointeur lui-même, en passage par référence) n'ajoute rien à l'interface (car la valeur a été copiée), mais indique qu'en interne, la fonction ne modifie pas la copie locale du paramètre (il s'agit d'une affectation unique). Pour cette raison, certains préfèrent utiliser constin parameters uniquement pour le passage par référence, où il modifie le contrat, mais pas pour le passage par valeur, où il expose l'implémentation.

C++

Méthodes

Afin de tirer parti de l' approche de conception par contrat pour les types définis par l'utilisateur (structures et classes), qui peuvent avoir des méthodes ainsi que des données membres, le programmeur peut baliser les méthodes d'instance comme constsi elles ne modifiaient pas les membres de données de l'objet. L'application du constqualificateur aux méthodes d'instance est donc une fonctionnalité essentielle pour la cohérence, et n'est pas disponible dans de nombreux autres langages orientés objet tels que Java et C# ou dans C++/CLI ou Managed Extensions pour C++ de Microsoft . Alors que les méthodes peuvent être appelées par des objets et des non- objets de la même manière, les non- méthodes ne peuvent être invoquées que par des non- objets. Le modificateur sur une méthode d'instance s'applique à l'objet pointé par le pointeur " ", qui est un argument implicite passé à toutes les méthodes d'instance. Ainsi, avoir des méthodes est un moyen d'appliquer la cohérence à l' argument de pointeur " " implicite comme à d'autres arguments. constconstconstconstconstconstthisconstthis

Cet exemple illustre :

classe C { int i ; public : int Get () const // Notez la balise "const" { return i ; } void Set ( int j ) // Notez l'absence de "const" { i = j ; } };
void Foo ( C & nonConstC , C const & constC ) { int y = nonConstC . Get (); // Ok int x = constC . Get (); // Ok : Get() est const
nonConstC . Set ( 10 ); // Ok : nonConstC est modifiable constC . Set ( 10 ); // Erreur ! Set() est une méthode non-const et constC est un objet qualifié const }

Dans le code ci-dessus, le thispointeur implicite " " a Set()le type " C *const"; alors que le thispointeur " " a le Get()type " C const *const", indiquant que la méthode ne peut pas modifier son objet via le thispointeur " ".

Souvent, le programmeur fournira à la fois une méthode a constet une constméthode non a avec le même nom (mais des utilisations éventuellement très différentes) dans une classe pour prendre en charge les deux types d'appelants. Considérez :

classe MyArray { int données [ 100 ]; public : int & Get ( int i ) { renvoyer données [ i ]; } int const & Get ( int i ) const { renvoyer données [ i ]; } };
void Foo ( MyArray & array , MyArray const & constArray ) { // Obtenir une référence à un élément de tableau // et modifier sa valeur référencée.
tableau . Get ( 5 ) = 42 ; // OK ! (Appels : int & MyArray::Get(int)) constArray . Get ( 5 ) = 42 ; // Erreur ! (Appels : int const & MyArray::Get(int) const) }

Le constcaractère -ness de l'objet appelant détermine quelle version de MyArray::Get()sera invoquée et donc si l'appelant reçoit ou non une référence avec laquelle il peut manipuler ou seulement observer les données privées de l'objet. Les deux méthodes ont techniquement des signatures différentes car leurs thispointeurs " " ont des types différents, ce qui permet au compilateur de choisir le bon. (Renvoyer une constréférence à un int, au lieu de simplement renvoyer la intvaleur par , peut être excessif dans la deuxième méthode, mais la même technique peut être utilisée pour des types arbitraires, comme dans la bibliothèque de modèles standard .)

Les échappatoires à la rectitude constitutionnelle

Il existe plusieurs failles dans la pure cohérence en C et C++. Elles existent principalement pour des raisons de compatibilité avec le code existant.

La première, qui ne s'applique qu'à C++, est l'utilisation de const_cast, qui permet au programmeur de supprimer le constqualificateur, rendant ainsi tout objet modifiable. La nécessité de supprimer le qualificateur apparaît lors de l'utilisation de code et de bibliothèques existants qui ne peuvent pas être modifiés mais qui ne sont pas const-corrects. Par exemple, considérez ce code :

// Prototype d'une fonction que nous ne pouvons pas modifier mais dont 
nous savons qu'elle ne modifie pas le pointeur passé. 
void LibraryFunc ( int * ptr , int size );
void CallLibraryFunc ( int const * ptr , int size ) { LibraryFunc ( ptr , size ); // Erreur ! Supprime le qualificateur const
int * nonConstPtr = const_cast < int *> ( ptr ); // Supprimer le qualificateur LibraryFunc ( nonConstPtr , size ); // OK }

Cependant, toute tentative de modification d'un objet qui est lui-même déclaré constau moyen d'un cast const entraîne un comportement indéfini selon la norme ISO C++. Dans l'exemple ci-dessus, si ptrfait référence à une variable globale, locale ou membre déclarée comme const, ou à un objet alloué sur le tas via new int const, le code n'est correct que si LibraryFuncne modifie vraiment pas la valeur pointée par ptr.

Le langage C a besoin d'une échappatoire car une certaine situation existe. Les variables avec une durée de stockage statique peuvent être définies avec une valeur initiale. Cependant, l'initialiseur ne peut utiliser que des constantes comme des constantes de chaîne et d'autres littéraux, et n'est pas autorisé à utiliser des éléments non constants comme des noms de variables, que les éléments d'initialisation soient déclarés constou non, ou que la variable de durée statique soit déclarée constou non. Il existe un moyen non portable d'initialiser une constvariable qui a une durée de stockage statique. En construisant soigneusement un transtypage sur le côté gauche d'une affectation ultérieure, une constvariable peut être écrite, supprimant efficacement l' constattribut et l'initialisant avec des éléments non constants comme d'autres constvariables et autres. Écrire dans une constvariable de cette manière peut fonctionner comme prévu, mais cela provoque un comportement indéfini et contredit sérieusement la constance :

size_t const bufferSize = 8 * 1024 ; size_t const userTextBufferSize ; // la valeur initiale dépend de const bufferSize, ne peut pas être initialisée ici
...
int setupUserTextBox ( textBox_t * defaultTextBoxType , rect_t * defaultTextBoxLocation ) { * ( size_t * ) & userTextBufferSize = bufferSize - sizeof ( struct textBoxControls ); // avertissement : peut fonctionner, mais non garanti par C ... }

Une autre faille s'applique à la fois au C et au C++. Plus précisément, les langages imposent que les pointeurs et les références de membres soient « superficiels » par rapport à la constnature de leurs propriétaires, c'est-à-dire qu'un objet contenant possède consttous constles membres, à l'exception des membres pointés (et référents) qui sont toujours mutables. Pour illustrer cela, considérons ce code C++ :

struct S { int val ; int * ptr ; };
void Foo ( S const & s ) { int i = 42 ; s . val = i ; // Erreur : s est const, donc val est un const int s . ptr = & i ; // Erreur : s est const, donc ptr est un pointeur const vers int * s . ptr = i ; // OK : les données pointées par ptr sont toujours mutables, // même si cela n'est parfois pas souhaitable }

Bien que l'objet spassé à Foo()soit constant, ce qui rend tous ses membres constants, le pointeur accessible via s.ptrest toujours modifiable, bien que cela puisse ne pas être souhaitable du point de vue de constl'exactitude, car sil peut être le seul propriétaire du pointeur. Pour cette raison, Meyers soutient que la valeur par défaut des pointeurs et des références de membres devrait être « profonde » const, qui pourrait être remplacée par un mutablequalificateur lorsque le pointeur n'appartient pas au conteneur, mais cette stratégie créerait des problèmes de compatibilité avec le code existant. Ainsi, pour des raisons historiques , cette faille reste ouverte en C et C++.

Cette dernière faille peut être fermée en utilisant une classe pour cacher le pointeur derrière une constinterface correcte, mais de telles classes ne prennent pas en charge la sémantique de copie habituelle d'un constobjet (ce qui implique que la classe contenant ne peut pas non plus être copiée par la sémantique habituelle) ou permettent d'autres failles en autorisant la suppression de la constpropriété -ness par copie involontaire ou intentionnelle.

Enfin, plusieurs fonctions de la bibliothèque standard C ne respectent pas la règle de la constance avant C23 , car elles acceptent un constpointeur vers une chaîne de caractères et renvoient un constpointeur non- vers une partie de la même chaîne. strstret strchrfont partie de ces fonctions. Certaines implémentations de la bibliothèque standard C++, comme celle de Microsoft tentent de combler cette lacune en fournissant deux versions surchargées de certaines fonctions : une constversion " " et une constversion " non- ".

Problèmes

L'utilisation du système de types pour exprimer la constance conduit à diverses complexités et problèmes, et a donc été critiquée et non adoptée en dehors de la famille étroite C composée de C, C++ et D. Java et C#, qui sont fortement influencés par C et C++, ont tous deux explicitement rejeté constles qualificateurs de type de style , exprimant à la place la constance par des mots-clés qui s'appliquent à l'identifiant ( finalen Java constet readonlyen C#). Même au sein de C et C++, l'utilisation de constvarie considérablement, certains projets et organisations l'utilisant systématiquement, tandis que d'autres l'évitent.

strchrproblème

Le constqualificateur de type pose des problèmes lorsque la logique d'une fonction ne sait pas si son entrée est constante ou non, mais renvoie une valeur qui doit être du même type qualifié qu'une entrée. En d'autres termes, pour ces fonctions, si l'entrée est constante (qualifiée const), la valeur de retour doit l'être également, mais si l'entrée est variable (qualifiée non - const), la valeur de retour doit l'être également. Étant donné que la signature de type de ces fonctions diffère, elle nécessite deux fonctions (ou potentiellement plus, dans le cas d'entrées multiples) avec la même logique – une forme de programmation générique .

Ce problème se pose même pour des fonctions simples dans la bibliothèque standard C, notamment strchr; cette observation est attribuée par Ritchie à Tom Plum au milieu des années 1980. La strchrfonction localise un caractère dans une chaîne ; formellement, elle renvoie un pointeur vers la première occurrence du caractère cdans la chaîne s, et en C classique (K&R C) son prototype est :

char * strchr ( char * s , int c );

La strchrfonction ne modifie pas la chaîne d'entrée, mais la valeur de retour est souvent utilisée par l'appelant pour modifier la chaîne, comme :

si ( p = strchr ( q , '/' )) * p = ' ' ;

Ainsi, d'une part, la chaîne d'entrée peut l'être const(puisqu'elle n'est pas modifiée par la fonction), et si la chaîne d'entrée l'est, constla valeur de retour doit l'être également - tout simplement parce qu'elle peut renvoyer exactement le pointeur d'entrée, si le premier caractère correspond - mais d'autre part, la valeur de retour ne doit pas l'être constsi la chaîne d'origine ne l'était pas const, puisque l'appelant peut souhaiter utiliser le pointeur pour modifier la chaîne d'origine.

En C++, cela se fait via une surcharge de fonction , généralement implémentée via un modèle , ce qui donne lieu à deux fonctions, de sorte que la valeur de retour a le même consttype qualifié que l'entrée :

char * strchr ( char * s , int c ); char const * strchr ( char const * s , int c );

Ceux-ci peuvent à leur tour être définis par un modèle :

modèle < T > T * strchr ( T * s , int c ) { ... }

En D, cela est géré via le inoutmot-clé, qui agit comme un caractère générique pour const, immuable ou non qualifié (variable), ce qui donne :

inout ( char )* strchr ( inout ( char )* s , int c );

Cependant, en C, aucune de ces deux opérations n'est possible, car C n'a pas de surcharge de fonction, et à la place, cela est géré en ayant une seule fonction où l'entrée est constante mais la sortie est accessible en écriture :

char * strchr ( char const * s , int c );

Cela permet d'utiliser du code C idiomatique, mais supprime le qualificateur const si l'entrée était effectivement qualifiée const, ce qui viole la sécurité des types. Cette solution a été proposée par Ritchie et adoptée par la suite. Cette différence est l'un des échecs de compatibilité entre C et C++ .

Depuis C23 , ce problème est résolu avec l'utilisation de fonctions génériques. strchret les autres fonctions affectées par le problème renverront un constpointeur si un pointeur leur a été transmis et un pointeur non qualifié si un pointeur non qualifié leur a été transmis.

D

Dans la version 2 du langage de programmation D , deux mots-clés relatifs à const existent. Le immutablemot-clé désigne des données qui ne peuvent pas être modifiées par une référence. Le constmot-clé désigne une vue non modifiable des données modifiables. Contrairement à C++ const, D constet immutablesont « profonds » ou transitifs , et tout ce qui est accessible via un objet constou immutableest respectivement constou immutable.

Exemple de const vs. immutable en D

int [] foo = new int [ 5 ]; // foo est mutable. const int [] bar = foo ; // bar est une vue constante de données mutables. immutable int [] baz = foo ; // Erreur : toutes les vues de données immuables doivent être immuables.
immutable int [] nums = new immutable ( int )[ 5 ]; // Aucune référence mutable à nums ne peut être créée. const int [] constNums = nums ; // Fonctionne. immutable est implicitement convertible en const. int [] mutableNums = nums ; // Erreur : Impossible de créer une vue mutable de données immuables.

Exemple de constante transitive ou profonde en D

classe Foo { Foo suivant ; int num ; }
immutable Foo foo = new immutable ( Foo ); foo . next . num = 5 ; // Ne compilera pas. foo.next est de type immutable(Foo). // foo.next.num est de type immutable(int).

Histoire

consta été introduit par Bjarne Stroustrup en C avec Classes , le prédécesseur de C++ , en 1981, et s'appelait à l'origine readonly. En ce qui concerne la motivation, Stroustrup écrit :

« Il remplissait deux fonctions : comme moyen de définir une constante symbolique qui obéit aux règles de portée et de type (c'est-à-dire sans utiliser de macro) et comme moyen de considérer qu'un objet en mémoire est immuable. »

La première utilisation, en tant qu'alternative aux macros, a été réalisée de manière analogue pour les macros de type fonction via le inlinemot-clé. Les pointeurs constants et la * constnotation ont été suggérés par Dennis Ritchie et donc adoptés.

consta ensuite été adopté en C dans le cadre de la normalisation, et apparaît dans C89 (et les versions ultérieures) avec l'autre qualificateur de type, volatile. Un autre qualificateur, noalias, a été suggéré lors de la réunion de décembre 1987 du comité X3J11, mais a été rejeté ; son objectif a finalement été atteint par le restrictmot-clé dans C99 . Ritchie n'était pas très favorable à ces ajouts, arguant qu'ils ne « portaient pas leur poids », mais n'a finalement pas plaidé pour leur suppression de la norme.

D a ensuite hérité constde C++, où il est connu comme un constructeur de type (et non un qualificateur de type ) et a ajouté deux autres constructeurs de type, immutableet inout, pour gérer les cas d'utilisation associés.

Autres langues

D'autres langages ne suivent pas C/C++ en ce qui concerne la constance du type, bien qu'ils aient souvent des constructions superficiellement similaires et puissent utiliser le constmot-clé. En général, ce mot-clé n'est utilisé que pour les constantes (objets constants).

C# possède un constmot-clé, mais avec une sémantique radicalement différente et plus simple : il s'agit d'une constante de compilation et ne fait pas partie du type.

Nim possède un constmot-clé similaire à celui de C# : il déclare également une constante de compilation plutôt que de faire partie du type. Cependant, dans Nim, une constante peut être déclarée à partir de n'importe quelle expression pouvant être évaluée au moment de la compilation. En C#, seuls les types intégrés de C# peuvent être déclarés comme telsconst ; les types définis par l'utilisateur, y compris les classes, les structures et les tableaux, ne peuvent pas l'être const.

Java n'a pas constde fonction, mais plutôt final, qui peut être appliquée aux déclarations de « variables » locales et s'applique à l' identifiant , et non au type. Il a une utilisation orientée objet différente pour les membres d'objet, ce qui est à l'origine du nom.

La spécification du langage Java considère constcomme un mot-clé réservé – c'est-à-dire qui ne peut pas être utilisé comme identifiant de variable – mais ne lui attribue aucune sémantique : c'est un mot réservé (il ne peut pas être utilisé dans les identifiants) mais pas un mot-clé (il n'a pas de signification particulière). Le mot-clé a été inclus comme un moyen pour les compilateurs Java de détecter et d'avertir de l'utilisation incorrecte des mots-clés C++. Un ticket de demande d'amélioration pour l'implémentation constde l'exactitude existe dans le processus de la communauté Java , mais a été fermé en 2005 sur la base de l'impossibilité de l'implémenter de manière rétrocompatible.

L' Ada 83 contemporaine avait indépendamment la notion d'objet constant et de constantmot-clé, avec des paramètres d'entrée et des paramètres de boucle implicitement constants. Ici, il constants'agit d'une propriété de l'objet, et non du type.

JavaScript possède une constdéclaration qui définit une variable à portée de bloc qui ne peut pas être réaffectée ni redéclarée. Elle définit une référence en lecture seule à une variable qui ne peut pas être redéfinie, mais dans certaines situations, la valeur de la variable elle-même peut potentiellement changer, par exemple si la variable fait référence à un objet et qu'une de ses propriétés est modifiée.

Plus d articles de Worldlex Wiki

Revenez a l index pour explorer davantage de pages sur l histoire, la science, la culture, la geographie et la societe en francais.

Explorer l index