C++
Spécificateurs de classe de stockage
Recherche…
Introduction
Les spécificateurs de classe de stockage sont des mots clés pouvant être utilisés dans les déclarations. Ils n'affectent pas le type de déclaration, mais modifient généralement la manière dont l'entité est stockée.
Remarques
Il existe six spécificateurs de classe de stockage, mais pas tous dans la même version du langage: auto
(jusqu'à C ++ 11), register
(jusqu'à C ++ 17), static
, thread_local
(depuis C ++ 11), extern
et mutable
.
Selon la norme,
Au plus, un spécificateur de classe de stockage doit apparaître dans une déclaration-spécificateur-seq donnée , sauf que
thread_local
peut apparaître avecstatic
ouextern
.
Une déclaration ne peut contenir aucun spécificateur de classe de stockage. Dans ce cas, le langage spécifie un comportement par défaut. Par exemple, par défaut, une variable déclarée à la portée du bloc a implicitement une durée de stockage automatique.
mutable
Un spécificateur qui peut être appliqué à la déclaration d'un membre de données non statique et non référencé d'une classe. Un membre mutable d'une classe n'est pas const
quand l'objet est const
.
class C {
int x;
mutable int times_accessed;
public:
C(): x(0), times_accessed(0) {
}
int get_x() const {
++times_accessed; // ok: const member function can modify mutable data member
return x;
}
void set_x(int x) {
++times_accessed;
this->x = x;
}
};
Une deuxième signification pour mutable
été ajoutée en C ++ 11. Quand il suit la liste de paramètres d'un lambda, il supprime le const
implicite sur l'opérateur d'appel de la fonction de lambda. Par conséquent, un lambda mutable peut modifier les valeurs des entités capturées par copie. Voir lambda mutable pour plus de détails.
std::vector<int> my_iota(int start, int count) {
std::vector<int> result(count);
std::generate(result.begin(), result.end(),
[start]() mutable { return start++; });
return result;
}
Notez que mutable
n'est pas un spécificateur de classe de stockage lorsqu'il est utilisé de cette manière pour former un lambda modifiable.
registre
Un spécificateur de classe de stockage qui indique au compilateur qu'une variable sera fortement utilisée. Le mot "register" est lié au fait qu'un compilateur pourrait choisir de stocker une telle variable dans un registre de CPU afin de pouvoir y accéder en moins de cycles d'horloge. Il était obsolète en C ++ 11.
register int i = 0;
while (i < 100) {
f(i);
int g = i*i;
i += h(i, g);
}
Les variables locales et les paramètres de fonction peuvent être déclarés register
. Contrairement à C, C ++ ne place aucune restriction sur ce qui peut être fait avec une variable de register
. Par exemple, il est valable de prendre l'adresse d'une variable de register
, mais cela peut empêcher le compilateur de stocker réellement une telle variable dans un registre.
Le register
mots clés est inutilisé et réservé. Un programme qui utilise le register
mots-clés est mal formé.
statique
Le spécificateur de classe de stockage static
a trois significations différentes.
Donne un lien interne à une variable ou une fonction déclarée dans la portée de l'espace de noms.
// internal function; can't be linked to static double semiperimeter(double a, double b, double c) { return (a + b + c)/2.0; } // exported to client double area(double a, double b, double c) { const double s = semiperimeter(a, b, c); return sqrt(s*(s-a)*(s-b)*(s-c)); }
Déclare une variable pour avoir une durée de stockage statique (sauf s'il s'agit de
thread_local
). Les variables d'espace de nommage sont implicitement statiques. Une variable locale statique est initialisée une seule fois, la première fois que le contrôle passe par sa définition et n'est pas détruite à chaque fois que son étendue est supprimée.void f() { static int count = 0; std::cout << "f has been called " << ++count << " times so far\n"; }
Appliqué à la déclaration d'un membre de la classe, déclare que ce membre est un membre statique .
struct S { static S* create() { return new S; } }; int main() { S* s = S::create(); }
Notez que dans le cas d'un membre de données statique d'une classe, 2 et 3 s'appliquent simultanément: le mot clé static
transforme le membre en un membre de données statique et en fait une variable avec une durée de stockage statique.
auto
Déclare une variable pour avoir une durée de stockage automatique. Il est redondant, car la durée de stockage automatique est déjà la valeur par défaut pour la portée du bloc et le spécificateur automatique n'est pas autorisé pour la portée de l'espace de noms.
void f() {
auto int x; // equivalent to: int x;
auto y; // illegal in C++; legal in C89
}
auto int z; // illegal: namespace-scope variable cannot be automatic
En C ++ 11, auto
signification auto
change complètement et n'est plus un spécificateur de classe de stockage, mais est plutôt utilisée pour la déduction de type .
externe
Le spécificateur de classe de stockage extern
peut modifier une déclaration de l'une des trois manières suivantes, en fonction du contexte:
Il peut être utilisé pour déclarer une variable sans la définir. En règle générale, cela est utilisé dans un fichier d'en-tête pour une variable qui sera définie dans un fichier de mise en œuvre distinct.
// global scope int x; // definition; x will be default-initialized extern int y; // declaration; y is defined elsewhere, most likely another TU extern int z = 42; // definition; "extern" has no effect here (compiler may warn)
Il donne un lien externe à une variable dans la portée de l’espace de noms, même si
const
ouconstexpr
aurait autrement eu un lien interne.// global scope const int w = 42; // internal linkage in C++; external linkage in C static const int x = 42; // internal linkage in both C++ and C extern const int y = 42; // external linkage in both C++ and C namespace { extern const int z = 42; // however, this has internal linkage since // it's in an unnamed namespace }
Elle redéfinit une variable à portée de bloc si elle a déjà été déclarée avec la liaison. Sinon, il déclare une nouvelle variable avec linkage, qui est un membre de l'espace de nommage le plus proche.
// global scope namespace { int x = 1; struct C { int x = 2; void f() { extern int x; // redeclares namespace-scope x std::cout << x << '\n'; // therefore, this prints 1, not 2 } }; } void g() { extern int y; // y has external linkage; refers to global y defined elsewhere }
Une fonction peut également être déclarée extern
, mais cela n'a aucun effet. Il est généralement utilisé comme un indice pour le lecteur qu'une fonction déclarée ici est définie dans une autre unité de traduction. Par exemple:
void f(); // typically a forward declaration; f defined later in this TU
extern void g(); // typically not a forward declaration; g defined in another TU
Dans le code ci-dessus, si f
était changé en extern
et g
en non- extern
, cela n'affecterait pas du tout l'exactitude ou la sémantique du programme, mais pourrait induire le lecteur en erreur.