Pourquoi l'initialisation des membres de données statiques doit-elle être en dehors de la classe?
class X
{
public:
int normalValue = 5; //NSDMI
static int i;
};
int X::i = 0;
Pourquoi le membre de données statique (ici "i") est-il seulement une déclaration, pas une définition?
Il est important de distinguer le initializer qui dit quelle est sa valeur initiale, et la definition . Ce code modifié est valide, avec l'initialiseur dans la définition de classe:
class X
{
public:
int normalValue = 5;
static const int i = 0; // declaration, with initializer
};
const int X::i; // definition
c'est-à-dire que ce qui doit être en dehors de la classe est une définition, pas l'initialisation.
En effet, une variable doit avoir une adresse en mémoire (à moins qu'elle ne soit utilisée que dans des situations limitées, telles que des expressions constantes au moment de la compilation.)
Une variable membre non statique existe dans l'objet dont il est membre. Son adresse dépend donc de l'adresse de l'objet qui la contient. Chaque fois que vous créez un nom X
name__, vous créez également une nouvelle variable X::normalValue
. La durée de vie du membre de données non statique commence par le constructeur de la classe. La syntaxe NSDMI n'a rien à voir avec l'adresse de la variable en mémoire, elle vous permet simplement de fournir une valeur initiale à un endroit, au lieu de la répéter dans chaque constructeur avec une liste d'initialisation de constructeur explicite.
D'autre part, une variable membre statique n'est pas contenue dans une instance de la classe, elle existe indépendamment de toute instance unique et existe depuis le début du programme, à une adresse fixe. Pour qu'une variable membre statique (ou tout autre objet global) obtienne une adresse unique, l'éditeur de liens doit voir exactement une définition de la variable statique, dans un seul fichier objet, et lui attribuer une adresse.
Etant donné qu'une variable statique nécessite exactement une définition dans un seul fichier objet, il n'est pas logique de permettre à cette définition d'être fournie à la classe, car les définitions de classe existent généralement dans les fichiers d'en-tête et sont incluses dans plusieurs fichiers objets. Ainsi, bien que vous puissiez fournir un initialiseur dans la classe, vous devez quand même définir le membre de données statique quelque part.
Vous pouvez également le regarder comme si vous déclariez une variable extern
name__:
namespace X {
extern int i;
}
Cela déclare la variable, mais il doit y avoir une définition quelque part dans le programme:
int X::i = 0;
Vous devez fournir une définition distincte pour un membre de données statique (si son odr-used , tel que défini dans C++ 11) simplement parce que cette définition doit résider quelque part - dans une et une seule unité de traduction. Les membres de données de classe statiques sont essentiellement des objets globaux (variables globales) déclarés dans l'étendue de la classe. Le compilateur souhaite que vous choisissiez une unité de traduction spécifique qui contiendra le "corps" réel de chaque objet global. C'est à vous de décider à quelle unité de traduction placer l'objet réel.
Un membre de classe "statique" est comme une variable allouée globalement (il n'est pas lié à une instance de classe unique), il doit donc résider dans un fichier objet (et être déclaré dans le fichier ".cpp") en tant que symbole, comme tout variable globale.
Un membre de classe simple (non statique) réside dans le bloc de mémoire alloué pour l'instance de classe.
La raison simple en est que les classes sont généralement déclarées dans en-tête fichiers, qui sont souvent inclus dans plusieurs fichiers cpp. Les membres de données statiques ont un lien externe et doivent être déclarés dans exactement une unité de traduction, ce qui les rend inaptes à être définis dans une classe.
Comme le souligne juanchopanza, les conditions suivantes sont autorisées:
struct A
{
const static int i = 1;
};
Cependant, ceci n'est qu'une déclaration pas une définition. Vous devez toujours le définir si vous souhaitez utiliser l'adresse de i
quelque part. Par exemple:
f(int);
g(int&);
X<A::i> x; // Okay without definition for template arguments
char a[A::i]; // Okay without definition, just using value as constant expression
&A::i; // Need a definition because I'm taking the address
f(A::i); // Okay without definition for pass by value
g(A::i); // Need a definition with pass by reference
Gardez à l'esprit qu'il est possible d'initialiser le membre de données statique au point de déclaration s'il est de type intégrale const ou de type énumération const:
A partir de la norme C++ 03, §9.4.2
Si un membre de données statique est de type énum intégral ou const constant, sa déclaration dans la définition de classe Peut spécifier un initialisateur de constante qui doit être une expression constante intégrale (5.19).
struct Foo {
static const int j = 42; // OK
};
Lorsque le compilateur génère un code binaire à partir d'une unité (simplification extrême: un fichier cpp et tous ses en-têtes inclus), il émet un symbole pour la variable statique et éventuellement un code d'initialisation pour cette variable.
Il est normal qu'un symbole de variable statique soit déclaré dans plusieurs unités, mais il n'est pas acceptable qu'il soit initialisé plusieurs fois.
Vous devez donc vous assurer que le code d’initialisation n’est émis que pour une seule unité. Cela signifie que la variable statique doit être définie dans exactement une unité.
Membre statique de données
#include<iostream.h>
#include<conio.h>
class static_var
{
static int count; //static member of class
public :
void incr_staticvar()
{
count++;
}
void outputc()
{
cout<<"Value of Static variable Count :- "<<count<<endl;
}
};
int static_var::count;
void main()
{
clrscr();
static_var obj1,obj2,obj3,obj4;
obj1.incr_staticvar();
obj2.incr_staticvar();
obj3.incr_staticvar();
obj4.incr_staticvar();
cout<<"\nAfter Increment of static variable by Four Different objects is :-\n";
obj1.outputc ( );
obj2.outputc ( );
obj3.outputc ( );
obj4.outputc ( );
getch();
}