Classes de stockage

Une classe de stockage dans le contexte des déclarations de variables C++ est un spécificateur de type qui régit la durée de vie, la liaison et l’emplacement de mémoire des objets. Un objet donné ne peut avoir qu'une seule classe de stockage. Les variables définies dans un bloc ont un stockage automatique, sauf indication contraire à l’aide des spécificateurs ou thread_local des externstaticspécificateurs. Les objets et variables automatiques n’ont aucune liaison ; ils ne sont pas visibles pour le code en dehors du bloc. La mémoire est allouée automatiquement lorsque l’exécution entre dans le bloc et qu’elle est dé-allouée lorsque le bloc est arrêté.

Notes

  • La mutable mot clé peut être considérée comme un spécificateur de classe de stockage. Toutefois, il est disponible uniquement dans la liste des membres d’une définition de classe.

  • Visual Studio 2010 et versions ultérieures : le auto mot clé n’est plus un spécificateur de classe de stockage C++ et le register mot clé est déconseillé. Visual Studio 2017 version 15.7 et ultérieure : (disponible en /std:c++17 mode et versions ultérieures) : le register mot clé est supprimé du langage C++. Son utilisation provoque un message de diagnostic :

    // c5033.cpp
    // compile by using: cl /c /std:c++17 c5033.cpp
    register int value; // warning C5033: 'register' is no longer a supported storage class
    

static

Le static mot clé peut être utilisé pour déclarer des variables et des fonctions dans l’étendue globale, l’étendue de l’espace de noms et l’étendue de classe. Les variables statiques peuvent également être déclarées dans la portée locale.

La durée statique signifie que l'objet ou la variable est alloué au démarrage du programme et est libéré à la fin de l'exécution du programme. La liaison externe signifie que le nom de la variable est visible à partir de l’extérieur du fichier où la variable est déclarée. À l’inverse, la liaison interne signifie que le nom n’est pas visible en dehors du fichier où la variable est déclarée. Par défaut, un objet ou une variable défini dans l'espace de noms global a une durée statique et une liaison externe. La static mot clé peut être utilisée dans les situations suivantes.

  1. Lorsque vous déclarez une variable ou une fonction au niveau de l’étendue de fichier (étendue globale et/ou d’espace de noms), l’mot clé static spécifie que la variable ou la fonction a une liaison interne. Lorsque vous déclarez une variable, la variable a une durée statique et le compilateur l'initialise à 0, sauf si vous spécifiez une autre valeur.

  2. Lorsque vous déclarez une variable dans une fonction, la static mot clé spécifie que la variable conserve son état entre les appels à cette fonction.

  3. Lorsque vous déclarez un membre de données dans une déclaration de classe, l’mot clé static spécifie qu’une copie du membre est partagée par toutes les instances de la classe. Un static membre de données doit être défini au niveau de l’étendue du fichier. Membre de données intégral que vous déclarez comme const static pouvant avoir un initialiseur.

  4. Lorsque vous déclarez une fonction membre dans une déclaration de classe, l’mot clé static spécifie que la fonction est partagée par toutes les instances de la classe. Une static fonction membre ne peut pas accéder à un membre d’instance, car la fonction n’a pas de pointeur implicite this . Pour accéder à un membre d’instance, déclarez la fonction avec un paramètre qui est un pointeur d’instance ou une référence.

  5. Vous ne pouvez pas déclarer les membres d’un union as static. Toutefois, un anonyme union déclaré globalement doit être déclaré staticexplicitement.

Cet exemple montre comment une variable déclarée static dans une fonction conserve son état entre les appels à cette fonction.

// static1.cpp
// compile with: /EHsc
#include <iostream>

using namespace std;
void showstat( int curr ) {
   static int nStatic;    // Value of nStatic is retained
                          // between each function call
   nStatic += curr;
   cout << "nStatic is " << nStatic << endl;
}

int main() {
   for ( int i = 0; i < 5; i++ )
      showstat( i );
}
nStatic is 0
nStatic is 1
nStatic is 3
nStatic is 6
nStatic is 10

Cet exemple montre l’utilisation dans static une classe.

// static2.cpp
// compile with: /EHsc
#include <iostream>

using namespace std;
class CMyClass {
public:
   static int m_i;
};

int CMyClass::m_i = 0;
CMyClass myObject1;
CMyClass myObject2;

int main() {
   cout << myObject1.m_i << endl;
   cout << myObject2.m_i << endl;

   myObject1.m_i = 1;
   cout << myObject1.m_i << endl;
   cout << myObject2.m_i << endl;

   myObject2.m_i = 2;
   cout << myObject1.m_i << endl;
   cout << myObject2.m_i << endl;

   CMyClass::m_i = 3;
   cout << myObject1.m_i << endl;
   cout << myObject2.m_i << endl;
}
0
0
1
1
2
2
3
3

L’exemple suivant montre une variable locale déclarée static dans une fonction membre. La static variable est disponible pour l’ensemble du programme ; toutes les instances du type partagent la même copie de la static variable.

// static3.cpp
// compile with: /EHsc
#include <iostream>
using namespace std;
struct C {
   void Test(int value) {
      static int var = 0;
      if (var == value)
         cout << "var == value" << endl;
      else
         cout << "var != value" << endl;

      var = value;
   }
};

int main() {
   C c1;
   C c2;
   c1.Test(100);
   c2.Test(100);
}
var != value
var == value

À compter de C++11, une static initialisation de variable locale est garantie d’être thread-safe. Cette fonctionnalité est parfois appelée statique magique. Toutefois, dans une application multithread, toutes les assignations suivantes doivent être synchronisées. La fonctionnalité d’initialisation statique thread-safe peut être désactivée à l’aide de l’indicateur /Zc:threadSafeInit- pour éviter de prendre une dépendance sur le CRT.

extern

Les objets et les variables déclarés comme extern déclarent un objet défini dans une autre unité de traduction ou dans une étendue englobante comme ayant une liaison externe. Pour plus d’informations, consultez extern et unités de traduction et liaison.

thread_local (C++11)

Une variable déclarée avec le thread_local spécificateur est accessible uniquement sur le thread sur lequel il est créé. La variable est créée lorsque le thread est créé et détruit lorsque le thread est détruit. Chaque thread possède sa propre copie de la variable. Sur Windows, thread_local est fonctionnellement équivalent à l’attribut spécifique __declspec( thread ) à Microsoft.

thread_local float f = 42.0; // Global namespace. Not implicitly static.

struct S // cannot be applied to type definition
{
    thread_local int i; // Illegal. The member must be static.
    thread_local static char buf[10]; // OK
};

void DoSomething()
{
    // Apply thread_local to a local variable.
    // Implicitly "thread_local static S my_struct".
    thread_local S my_struct;
}

Remarques sur le thread_local spécificateur :

  • Les variables locales de thread initialisées dynamiquement dans les DLL peuvent ne pas être correctement initialisées sur tous les threads appelants. Pour plus d’informations, consultez thread.

  • Le thread_local spécificateur peut être combiné avec static ou extern.

  • Vous ne pouvez s’appliquer thread_local qu’aux déclarations et définitions de données ; thread_local ne peut pas être utilisé sur les déclarations de fonction ou les définitions.

  • Vous pouvez spécifier thread_local uniquement sur les éléments de données avec une durée de stockage statique, qui inclut des objets de données globaux (à la fois static et extern), des objets statiques locaux et des membres de données statiques de classes. Toute variable locale déclarée thread_local est implicitement statique si aucune autre classe de stockage n’est fournie ; en d’autres termes, l’étendue thread_local de bloc est équivalente à thread_local static.

  • Vous devez spécifier thread_local à la fois pour la déclaration et la définition d'un objet local de thread, que la déclaration et la définition se produisent dans le même fichier ou dans des fichiers séparés.

  • Nous vous déconseillons d’utiliser thread_local des variables avec std::launch::async. Pour plus d’informations, consultez <future> les fonctions.

Sur Windows, thread_local équivaut fonctionnellement à __declspec(thread) ceci près que *__declspec(thread)* peut être appliqué à une définition de type et est valide dans le code C. Dans la mesure du possible, utilisez-le thread_local parce qu’il fait partie de la norme C++ et est donc plus portable.

inscription

Visual Studio 2017 version 15.3 et ultérieure (disponible en /std:c++17 mode et versions ultérieures) : le register mot clé n’est plus une classe de stockage prise en charge. Son utilisation provoque un diagnostic. Le mot clé est toujours réservé dans la norme pour une utilisation ultérieure.

   register int val; // warning C5033: 'register' is no longer a supported storage class

Exemple : initialisation automatique et statique

Une variable automatique ou un objet local est initialisé chaque fois que l'ordre d'exécution atteint sa définition. Une variable automatique ou un objet statique est initialisé la première fois que l'ordre d'exécution atteint sa définition.

Prenons l'exemple suivant, qui définit une classe qui stocke l'initialisation et la destruction des objets, puis définit trois objets, I1, I2, et I3:

// initialization_of_objects.cpp
// compile with: /EHsc
#include <iostream>
#include <string.h>
using namespace std;

// Define a class that logs initializations and destructions.
class InitDemo {
public:
    InitDemo( const char *szWhat );
    ~InitDemo();

private:
    char *szObjName;
    size_t sizeofObjName;
};

// Constructor for class InitDemo
InitDemo::InitDemo( const char *szWhat ) :
    szObjName(NULL), sizeofObjName(0) {
    if ( szWhat != 0 && strlen( szWhat ) > 0 ) {
        // Allocate storage for szObjName, then copy
        // initializer szWhat into szObjName, using
        // secured CRT functions.
        sizeofObjName = strlen( szWhat ) + 1;

        szObjName = new char[ sizeofObjName ];
        strcpy_s( szObjName, sizeofObjName, szWhat );

        cout << "Initializing: " << szObjName << "\n";
    }
    else {
        szObjName = 0;
    }
}

// Destructor for InitDemo
InitDemo::~InitDemo() {
    if( szObjName != 0 ) {
        cout << "Destroying: " << szObjName << "\n";
        delete szObjName;
    }
}

// Enter main function
int main() {
    InitDemo I1( "Auto I1" ); {
        cout << "In block.\n";
        InitDemo I2( "Auto I2" );
        static InitDemo I3( "Static I3" );
    }
    cout << "Exited block.\n";
}
Initializing: Auto I1
In block.
Initializing: Auto I2
Initializing: Static I3
Destroying: Auto I2
Exited block.
Destroying: Auto I1
Destroying: Static I3

Cet exemple montre comment et quand les objets I1, I2et I3 sont initialisés et quand ils sont détruits.

Il existe plusieurs points à noter sur le programme :

  • Tout d’abord, I1 et I2 sont automatiquement détruits lorsque le flux de contrôle quitte le bloc dans lequel ils sont définis.

  • Deuxièmement, en C++, il n’est pas nécessaire de déclarer des objets ou des variables au début d’un bloc. En outre, ces objets sont initialisés uniquement lorsque l'ordre d'exécution atteint leurs définitions. (I2 et I3 sont des exemples de ces définitions.) La sortie s’affiche exactement quand elles sont initialisées.

  • Enfin, les variables locales statiques telles que I3 conserver leurs valeurs pendant l’exécution du programme, mais sont détruites à mesure que le programme se termine.

Voir aussi

Déclarations et définitions