Pointer declaration
Déclare une variable de type pointeur ou pointeur-vers-membre.
Table des matières |
Syntaxe
Une déclaration de pointeur est toute déclaration simple dont le déclarateur a la forme
*
attr
(optionnel)
cv
(optionnel)
déclarateur
|
(1) | ||||||||
spécificateur-de-nom-imbriqué
*
attr
(optionnel)
cv
(optionnel)
déclarateur
|
(2) | ||||||||
S
.
C
dont le type est déterminé par la séquence de spécificateurs de déclaration
S
.
| nested-name-specifier | - |
une
séquence de noms et d'opérateurs de résolution de portée
::
|
| attr | - | (since C++11) une liste d' attributs |
| cv | - | qualification const/volatile qui s'applique au pointeur en cours de déclaration (et non au type pointé, dont les qualifications font partie de la séquence de spécificateurs de déclaration) |
| declarator | - | tout déclarateur |
Il n'existe pas de pointeurs vers les références et il n'existe pas de pointeurs vers les champs de bits . Typiquement, les mentions de "pointeurs" sans précision n'incluent pas les pointeurs vers les membres (non statiques).
Pointeurs
Chaque valeur de type pointeur est l'une des suivantes :
- un pointeur vers un objet ou une fonction (auquel cas on dit que le pointeur pointe vers l'objet ou la fonction), ou
- un pointeur après la fin d'un objet , ou
- la valeur de pointeur nul pour ce type, ou
- une valeur de pointeur invalide .
Un pointeur qui pointe vers un objet représente l'adresse du premier octet en mémoire occupé par l'objet. Un pointeur situé après la fin d'un objet représente l'adresse du premier octet en mémoire après la fin du stockage occupé par l'objet.
Notez que deux pointeurs qui représentent la même adresse peuvent néanmoins avoir des valeurs différentes.
struct C { int x, y; } c; int* px = &c.x; // la valeur de px est "pointeur vers c.x" int* pxe= px + 1; // la valeur de pxe est "pointeur après la fin de c.x" int* py = &c.y; // la valeur de py est "pointeur vers c.y" assert(pxe == py); // == teste si deux pointeurs représentent la même adresse // peut ou non se déclencher *pxe = 1; // comportement indéfini même si l'assertion ne se déclenche pas
L'indirection via une valeur de pointeur non valide et le passage d'une valeur de pointeur non valide à une fonction de désallocation ont un comportement indéfini. Toute autre utilisation d'une valeur de pointeur non valide a un comportement défini par l'implémentation. Certaines implémentations peuvent définir que la copie d'une valeur de pointeur non valide provoque une erreur d'exécution générée par le système.
Pointeurs vers des objets
Un pointeur vers un objet peut être initialisé avec la valeur de retour de l' opérateur d'adresse appliqué à toute expression de type objet, y compris un autre type pointeur :
int n; int* np = &n; // pointeur vers int int* const* npp = &np; // pointeur non constant vers un pointeur constant vers un int non constant int a[2]; int (*ap)[2] = &a; // pointeur vers un tableau d'int struct S { int n; }; S s = {1}; int* sp = &s.n; // pointeur vers l'int qui est un membre de s
Les pointeurs peuvent apparaître comme opérandes de l'opérateur de déréférencement intégré (opérateur unaire operator * ), qui renvoie l' expression lvalue identifiant l'objet pointé :
int n; int* p = &n; // pointeur vers n int& r = *p; // la référence est liée à l'expression lvalue qui identifie n r = 7; // stocke l'entier 7 dans n std::cout << *p; // conversion implicite lvalue-vers-rvalue lit la valeur depuis n
Les pointeurs vers des objets de classe peuvent également apparaître comme opérandes gauches des opérateurs d'accès membre
operator->
et
operator->*
.
En raison de la conversion implicite array-to-pointer , un pointeur vers le premier élément d'un tableau peut être initialisé avec une expression de type tableau :
int a[2]; int* p1 = a; // pointeur vers le premier élément a[0] (un int) du tableau a int b[6][3][8]; int (*p2)[3][8] = b; // pointeur vers le premier élément b[0] du tableau b, // qui est un tableau de 3 tableaux de 8 int
En raison de la conversion implicite de dérivé-vers-base pour les pointeurs, un pointeur vers une classe de base peut être initialisé avec l'adresse d'une classe dérivée :
struct Base {}; struct Derived : Base {}; Derived d; Base* p = &d;
` et contient des termes spécifiques au C++. Seul le texte environnant aurait été traduit, mais dans ce cas, il n'y a pas de texte à traduire en dehors du code.
Si
Derived
est
polymorphe
, un tel pointeur peut être utilisé pour effectuer des
appels de fonctions virtuelles
.
Certains opérateurs d'addition, de soustraction , d'incrémentation et de décrémentation sont définis pour les pointeurs vers des éléments de tableaux : ces pointeurs satisfont aux exigences LegacyRandomAccessIterator et permettent aux algorithmes de la bibliothèque C++ de fonctionner avec des tableaux bruts.
Opérateurs de comparaison sont définis pour les pointeurs vers des objets dans certaines situations : deux pointeurs qui représentent la même adresse se comparent égaux, deux valeurs de pointeur nul se comparent égales, les pointeurs vers des éléments d'un même tableau se comparent de la même manière que les indices de tableau de ces éléments, et les pointeurs vers des membres de données non statiques avec le même accès membre se comparent dans l'ordre de déclaration de ces membres.
De nombreuses implémentations fournissent également un ordre total strict des pointeurs de provenance aléatoire, par exemple s'ils sont implémentés comme des adresses dans un espace d'adressage virtuel continu. Les implémentations qui ne le font pas (par exemple, lorsque tous les bits du pointeur ne font pas partie d'une adresse mémoire et doivent être ignorés pour la comparaison, ou qu'un calcul supplémentaire est nécessaire, ou autrement le pointeur et l'entier n'ont pas une relation un à un), fournissent une spécialisation de std::less pour les pointeurs qui offre cette garantie. Cela permet d'utiliser tous les pointeurs de provenance aléatoire comme clés dans les conteneurs associatifs standards tels que std::set ou std::map .
Pointeurs vers void
Un pointeur vers un objet de n'importe quel type peut être
implicitement converti
en pointeur vers (éventuellement
cv-qualifié
)
void
; la valeur du pointeur reste inchangée. La conversion inverse, qui nécessite
static_cast
ou
un cast explicite
, restitue la valeur originale du pointeur :
int n = 1; int* p1 = &n; void* pv = p1; int* p2 = static_cast<int*>(pv); std::cout << *p2 << '\n'; // affiche 1
Si le pointeur original pointe vers un sous-objet de classe de base dans un objet d'un type polymorphe,
dynamic_cast
peut être utilisé pour obtenir un
void
*
qui pointe vers l'objet complet du type le plus dérivé.
Les pointeurs vers void ont la même taille, la même représentation et le même alignement que les pointeurs vers char .
Les pointeurs vers
void
sont utilisés pour transmettre des objets de type inconnu, ce qui est courant dans les interfaces C :
std::malloc
retourne
void
*
,
std::qsort
attend une fonction de rappel fournie par l'utilisateur qui accepte deux arguments
const
void
*
.
pthread_create
attend une fonction de rappel fournie par l'utilisateur qui accepte et retourne
void
*
. Dans tous les cas, il est de la responsabilité de l'appelant de convertir le pointeur vers le type correct avant utilisation.
Pointeurs vers des fonctions
Un pointeur vers fonction peut être initialisé avec l'adresse d'une fonction non-membre ou d'une fonction membre statique. En raison de la conversion implicite fonction-vers-pointeur , l'opérateur d'adresse est optionnel :
void f(int); void (*p1)(int) = &f; void (*p2)(int) = f; // identique à &f
Contrairement aux fonctions ou aux références de fonctions, les pointeurs vers des fonctions sont des objets et peuvent donc être stockés dans des tableaux, copiés, assignés, etc.
void (a[10])(int); // Erreur : tableau de fonctions void (&a[10])(int); // Erreur : tableau de références void (*a[10])(int); // OK : tableau de pointeurs vers des fonctions
Note : les déclarations impliquant des pointeurs vers des fonctions peuvent souvent être simplifiées avec des alias de type :
using F = void(int); // alias de type nommé pour simplifier les déclarations F a[10]; // Erreur : tableau de fonctions F& a[10]; // Erreur : tableau de références F* a[10]; // OK : tableau de pointeurs vers des fonctions
Un pointeur vers une fonction peut être utilisé comme opérande gauche de l' opérateur d'appel de fonction , ceci invoque la fonction pointée :
int f(int n) { std::cout << n << '\n'; return n * n; } int main() { int (*p)(int) = f; int x = p(7); }
Le déréférencement d'un pointeur de fonction donne la lvalue identifiant la fonction pointée :
int f(); int (*p)() = f; // le pointeur p pointe vers f int (&r)() = *p; // la lvalue qui identifie f est liée à une référence r(); // fonction f invoquée via une référence lvalue (*p)(); // fonction f invoquée via la lvalue de fonction p(); // fonction f invoquée directement via le pointeur
Un pointeur vers une fonction peut être initialisé à partir d'un ensemble de surcharges qui peut inclure des fonctions, des spécialisations de modèles de fonction et des modèles de fonction, si une seule surcharge correspond au type du pointeur (voir adresse d'une fonction surchargée pour plus de détails) :
template<typename T> T f(T n) { return n; } double f(double n) { return n; } int main() { int (*p)(int) = f; // instancie et sélectionne f<int> }
Les opérateurs de comparaison d'égalité sont définis pour les pointeurs vers des fonctions (ils comparent égaux s'ils pointent vers la même fonction).
Pointeurs vers les membres
Pointeurs vers les membres de données
Un pointeur vers un membre objet non statique
m
qui est membre de la classe
C
peut être initialisé exactement avec l'expression
&
C
::
m
. Des expressions telles que
&
(
C
::
m
)
ou
&
m
à l'intérieur d'une fonction membre de
C
ne forment pas des pointeurs vers des membres.
Un tel pointeur peut être utilisé comme opérande droit des opérateurs d'accès membre via pointeur operator. * et operator - > * :
Le pointeur vers un membre de données d'une classe de base accessible, non ambiguë et non virtuelle peut être implicitement converti en pointeur vers le même membre de données d'une classe dérivée :
struct Base { int m; }; struct Derived : Base {}; int main() { int Base::* bp = &Base::m; int Derived::* dp = bp; Derived d; d.m = 1; std::cout << d.*dp << ' ' << d.*bp << '\n'; // affiche 1 1 }
La conversion dans la direction opposée, d'un pointeur vers un membre de données d'une classe dérivée vers un pointeur vers un membre de données d'une classe de base non virtuelle non ambiguë, est autorisée avec
static_cast
et
explicit cast
, même si la classe de base ne possède pas ce membre (mais la classe la plus dérivée le possède, lorsque le pointeur est utilisé pour l'accès) :
Le type pointé par un pointeur-vers-membre peut être lui-même un pointeur-vers-membre : les pointeurs vers les membres peuvent être multi-niveaux, et peuvent être qualifiés différemment par cv à chaque niveau. Les combinaisons multi-niveaux mixtes de pointeurs et de pointeurs-vers-membres sont également autorisées :
struct A { int m; // pointeur const vers un membre non-const int A::* const p; }; int main() { // pointeur non-const vers un membre de données qui est un pointeur const vers un membre non-const int A::* const A::* p1 = &A::p; const A a = {1, &A::m}; std::cout << a.*(a.*p1) << '\n'; // affiche 1 // pointeur non-const régulier vers un pointeur-const-vers-membre int A::* const* p2 = &a.p; std::cout << a.**p2 << '\n'; // affiche 1 }
Pointeurs vers les fonctions membres
Un pointeur vers une fonction membre non statique
f
qui est membre de la classe
C
peut être initialisé exactement avec l'expression
&
C
::
f
. Des expressions telles que
&
(
C
::
f
)
ou
&
f
à l'intérieur de la fonction membre de
C
ne forment pas des pointeurs vers des fonctions membres.
Un tel pointeur peut être utilisé comme opérande droit des opérateurs d'accès membre via pointeur operator. * et operator - > * . L' expression résultante ne peut être utilisée que comme opérande gauche d'un opérateur d'appel de fonction :
struct C { void f(int n) { std::cout << n << '\n'; } }; int main() { void (C::* p)(int) = &C::f; // pointeur vers la fonction membre f de la classe C C c; (c.*p)(1); // affiche 1 C* cp = &c; (cp->*p)(2); // affiche 2 }
Le pointeur vers une fonction membre d'une classe de base peut être
implicitement converti
en pointeur vers la même fonction membre d'une classe dérivée :
struct Base { void f(int n) { std::cout << n << '\n'; } }; struct Derived : Base {}; int main() { void (Base::* bp)(int) = &Base::f; void (Derived::* dp)(int) = bp; Derived d; (d.*dp)(1); (d.*bp)(2); }
La conversion dans la direction opposée, d'un pointeur vers une fonction membre d'une classe dérivée vers un pointeur vers une fonction membre d'une classe de base non virtuelle non ambiguë, est autorisée avec
static_cast
et
explicit cast
, même si la classe de base ne possède pas cette fonction membre (mais la classe la plus dérivée la possède, lorsque le pointeur est utilisé pour l'accès) :
struct Base {}; struct Derived : Base { void f(int n) { std::cout << n << '\n'; } }; int main() { void (Derived::* dp)(int) = &Derived::f; void (Base::* bp)(int) = static_cast<void (Base::*)(int)>(dp); Derived d; (d.* bp)(1); // correct : affiche 1 Base b; (b.* bp)(2); // comportement indéfini }
Les pointeurs vers les fonctions membres peuvent être utilisés comme callbacks ou comme objets fonction, souvent après application de std::mem_fn ou std::bind :
#include <algorithm> #include <cstddef> #include <functional> #include <iostream> #include <string> int main() { std::vector<std::string> v = {"a", "ab", "abc"}; std::vector<std::size_t> l; transform(v.begin(), v.end(), std::back_inserter(l), std::mem_fn(&std::string::size)); for (std::size_t n : l) std::cout << n << ' '; std::cout << '\n'; }
Sortie :
1 2 3
Pointeurs nuls
Les pointeurs de chaque type ont une valeur spéciale connue sous le nom de null pointer value de ce type. Un pointeur dont la valeur est null ne pointe vers aucun objet ni fonction (le comportement du déréférencement d'un pointeur null est indéfini), et se compare comme égal à tous les pointeurs du même type dont la valeur est également null .
Une constante de pointeur nul peut être utilisée pour initialiser un pointeur à null ou pour assigner la valeur null à un pointeur existant, c'est l'une des valeurs suivantes :
- Un littéral entier de valeur zéro.
|
(depuis C++11) |
La macro NULL peut également être utilisée, elle se développe en une constante de pointeur nul définie par l'implémentation.
Zero-initialization et value-initialization initialisent également les pointeurs à leurs valeurs nulles.
Les pointeurs nuls peuvent être utilisés pour indiquer l'absence d'un objet (par exemple std::function::target() ), ou comme indicateurs d'autres conditions d'erreur (par exemple dynamic_cast ). En général, une fonction qui reçoit un argument pointeur doit presque toujours vérifier si la valeur est nulle et traiter ce cas différemment (par exemple, l' expression delete ne fait rien lorsqu'un pointeur nul est passé).
Pointeurs invalides
Une valeur de pointeur p est valide dans le contexte de une évaluation e si l'une des conditions suivantes est satisfaite :
- p est une valeur de pointeur nul.
- p est un pointeur vers une fonction.
- p est un pointeur vers ou au-delà de la fin d'un objet o , et e est dans la durée de la région de stockage pour o .
Si une valeur de pointeur p est utilisée dans une évaluation e , et que p n'est pas valide dans le contexte de e , alors :
- Si e est une indirection ou un appel à une fonction de désallocation , le comportement est indéfini.
- Sinon, le comportement est défini par l'implémentation.
int* f() { int obj; int* local_ptr = new (&obj) int; *local_ptr = 1; // OK, l'évaluation « *local_ptr » se trouve // dans la durée de stockage de « obj » return local_ptr; } int* ptr = f(); // la durée de stockage de « obj » est expirée, // donc « ptr » est un pointeur invalide dans les contextes suivants int* copy = ptr; // comportement défini par l'implémentation *ptr = 2; // comportement indéfini : indirection d'un pointeur invalide delete ptr; // comportement indéfini : libération de stockage depuis un pointeur invalide
Constance
-
Si
cv
apparaît avant
*dans la déclaration de pointeur, il fait partie de la séquence de spécificateurs de déclaration et s'applique à l'objet pointé. -
Si
cv
apparaît après
*dans la déclaration de pointeur, il fait partie du déclarateur et s'applique au pointeur qui est déclaré.
| Syntaxe | Signification |
|---|---|
| const T * | pointeur vers un objet constant |
| T const * | pointeur vers un objet constant |
| T * const | pointeur constant vers un objet |
| const T * const | pointeur constant vers un objet constant |
| T const * const | pointeur constant vers un objet constant |
// pc est un pointeur non-const vers un int const // cpc est un pointeur const vers un int const // ppc est un pointeur non-const vers un pointeur non-const vers un int const const int ci = 10, *pc = &ci, *const cpc = pc, **ppc; // p est un pointeur non-const vers un int non-const // cp est un pointeur const vers un int non-const int i, *p, *const cp = &i; i = ci; // correct : la valeur de l'int const est copiée dans un int non-const *cp = ci; // correct : l'int non-const (pointé par un pointeur const) peut être modifié pc++; // correct : le pointeur non-const (vers int const) peut être modifié pc = cpc; // correct : le pointeur non-const (vers int const) peut être modifié pc = p; // correct : le pointeur non-const (vers int const) peut être modifié ppc = &pc; // correct : l'adresse d'un pointeur vers int const est un pointeur vers pointeur vers int const ci = 1; // erreur : l'int const ne peut pas être modifié ci++; // erreur : l'int const ne peut pas être modifié *pc = 2; // erreur : l'int const pointé ne peut pas être modifié cp = &ci; // erreur : le pointeur const (vers int non-const) ne peut pas être modifié cpc++; // erreur : le pointeur const (vers int const) ne peut pas être modifié p = pc; // erreur : un pointeur vers int non-const ne peut pas pointer vers un int const ppc = &p; // erreur : un pointeur vers pointeur vers int const ne peut pas pointer vers // un pointeur vers int non-const
En général, la conversion implicite d'un pointeur multi-niveau vers un autre suit les règles décrites dans les conversions de qualification .
Type de pointeur composite
Lorsqu'un opérande d'un opérateur de comparaison ou l'un des deuxième et troisième opérandes d'un opérateur conditionnel est un pointeur ou un pointeur-vers-membre, un type de pointeur composite est déterminé comme étant le type commun de ces opérandes.
Étant donné deux opérandes
p1
et
p2
ayant respectivement les types
T1
et
T2
,
p1
et
p2
ne peuvent avoir un type pointeur composite que si l'une des conditions suivantes est satisfaite :
|
(jusqu'à C++14) | ||
|
(depuis C++14) |
Le
type de pointeur composite
C
de
p1
et
p2
est déterminé comme suit :
|
(jusqu'en C++11) |
|
(depuis C++11) |
- Sinon, si toutes les conditions suivantes sont satisfaites :
-
-
T1ouT2est un « pointeur vers cv1 void ». -
L'autre type est « pointeur vers
cv2
T», oùTest un type objet ou void .
-
-
Cest un « pointeur vers cv12 void », où cv12 est l'union de cv1 et cv2 .
|
(depuis C++17) |
- Sinon, si toutes les conditions suivantes sont satisfaites :
-
-
T1est « pointeur versC1». -
T2est « pointeur versC2». -
L'un des
C1etC2est lié par référence à l'autre.
-
-
Cest-
le
type combiné de qualifications
de
T1etT2, siC1est lié par référence àC2, ou -
le type combiné de qualifications de
T2etT1, siC2est lié par référence àC1.
-
le
type combiné de qualifications
de
|
(depuis C++17) |
- Sinon, si toutes les conditions suivantes sont satisfaites :
-
-
T1est « pointeur vers membre deC1de type non-fonctionM1». -
T2est « pointeur vers membre deC2de type non-fonctionM2» -
M1etM2sont identiques sauf pour les qualifications cv de plus haut niveau. -
L'un des
C1etC2est lié par référence à l'autre.
-
-
Cest-
le type combiné des qualifications de
T2etT1, siC1est lié par référence àC2, ou -
le type combiné des qualifications de
T1etT2, siC2est lié par référence àC1.
-
le type combiné des qualifications de
-
Sinon, si
T1etT2sont des types similaires ,Cest le type combiné des qualifications deT1etT2. -
Sinon,
p1
et
p2
n'ont pas de type pointeur composite, un programme qui nécessite la détermination de
Cpour un tel type est mal formé.
using p = void*; using q = const int*; // La détermination du type de pointeur composite de « p » et « q » // relève du cas [« pointeur vers cv1 void » et « pointeur vers cv2 T »] : // cv1 = vide, cv2 = const, cv12 = const // substitution de « cv12 = const » dans « pointeur vers cv12 void » : // le type de pointeur composite est « const void* » using pi = int**; using pci = const int**; // La détermination du type de pointeur composite de « pi » et « pci » // relève du cas [pointeurs vers des types similaires « C1 » et « C2 »] : // C1 = int*, C2 = const int* // ce sont des types référence-liés (dans les deux directions) car ils sont similaires // le type de pointeur composite est le type combiné de qualifications // de « p1 » et « pc1 » (ou celui de « pci » et « pi ») : « const int** »
Rapports de défauts
Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.
| DR | Applicable à | Comportement publié | Comportement corrigé |
|---|---|---|---|
| CWG 73 | C++98 |
un pointeur vers un objet ne compare jamais égal
à un pointeur vers un élément après la fin d'un tableau |
pour les pointeurs non nuls et non fonction,
compare les adresses qu'ils représentent |
| CWG 903 | C++98 |
toute expression constante entière qui
s'évalue à 0 était une constante de pointeur nul |
limité aux littéraux
entiers de valeur 0 |
| CWG 1438 | C++98 |
le comportement de l'utilisation d'une valeur de pointeur
invalide de quelque manière que ce soit était indéfini |
les comportements autres que l'indirection et
le passage aux fonctions de désallocation sont définis par l'implémentation |
|
CWG 1512
( N3624 ) |
C++98 |
la règle du type de pointeur composite était incomplète, et donc
ne permettait pas la comparaison entre int ** et const int ** |
rendue complète |
| CWG 2206 | C++98 |
un pointeur vers
void
et un pointeur vers
fonction avaient un type de pointeur composite |
ils n'ont pas un tel type |
| CWG 2381 | C++17 |
les conversions de pointeurs de fonction n'étaient pas autorisées
lors de la détermination du type de pointeur composite |
autorisées |
| CWG 2822 | C++98 |
atteindre la fin de la durée de vie d'une région
de stockage pouvait invalider les valeurs de pointeur |
la validité des pointeurs est basée
sur le contexte d'évaluation |
| CWG 2933 | C++98 | les pointeurs vers des fonctions étaient toujours invalides | ils sont toujours valides |
Voir aussi
|
Documentation C
pour
Déclaration de pointeur
|