web-dev-qa-db-fra.com

Variable membre statique C ++ et son initialisation


Pour les variables membres statiques dans la classe C++ - l'initialisation se fait en dehors de la classe. Je me demande pourquoi? Un raisonnement/contrainte logique pour cela? Ou s'agit-il d'une implémentation purement héritée - que la norme ne veut pas corriger?

Je pense qu'avoir une initialisation dans la classe est plus "intuitif" et moins déroutant. Cela donne aussi le sens à la fois statique et global de la variable. Par exemple, si vous voyez le membre const statique.

47
kumar_m_kiran

Fondamentalement, cela est dû au fait que les membres statiques doivent être définis dans exactement une unité de traduction, afin de ne pas violer la règle à définition unique . Si la langue permettait quelque chose comme:

struct Gizmo
{
  static string name = "Foo";
};

alors name serait défini dans chaque unité de traduction que #includes ce fichier d'en-tête.

C++ vous permet de définir les membres statiques intégraux dans la déclaration, mais vous devez toujours inclure une définition dans une seule unité de traduction, mais ce n'est qu'un raccourci ou sucre syntaxique. Donc, cela est autorisé:

struct Gizmo
{
  static const int count = 42;
};

Tant que a) l'expression est const intégrale ou de type énumération, b) l'expression peut être évaluée au moment de la compilation, et c) il y a toujours une définition quelque part qui ne viole pas la règle d'une définition:

fichier: gizmo.cpp

#include "gizmo.h"

const int Gizmo::count;
37
John Dibling

En C++ depuis le début des temps, la présence d'un initialiseur était un attribut exclusif de l'objet définition, c'est-à-dire qu'une déclaration avec un initialiseur est toujours un définition = (presque toujours).

Comme vous devez le savoir, chaque objet externe utilisé dans le programme C++ doit être défini une fois et une seule fois dans une seule unité de traduction. Autoriser les initialiseurs en classe pour les objets statiques irait immédiatement à l'encontre de cette convention: les initialiseurs iraient dans les fichiers d'en-tête (où résident généralement les définitions de classe) et généreraient ainsi plusieurs définitions du même objet statique (une pour chaque unité de traduction qui inclut le fichier d'en-tête). ). C'est, bien sûr, inacceptable. Pour cette raison, l'approche de déclaration pour les membres de classe statiques est laissée parfaitement "traditionnelle": vous seulement déclarez dans le fichier d'en-tête (c'est-à-dire aucun initialiseur autorisé), puis vous définissez = dans une unité de traduction de votre choix (éventuellement avec un initialiseur).

Une exception à cette règle a été faite pour les membres de la classe statique const de type intégral ou enum, car ces entrées peuvent pour les expressions de constante intégrales (ICE). L'idée principale des ICE est qu'ils sont évalués au moment de la compilation et ne dépendent donc pas des définitions des objets impliqués. C'est pourquoi cette exception était possible pour les types intégraux ou énumérés. Mais pour d'autres types, cela contredirait simplement les principes de base de déclaration/définition du C++.

12
AnT

C'est à cause de la façon dont le code est compilé. Si vous l'initialisiez dans la classe, qui se trouve souvent dans l'en-tête, chaque fois que l'en-tête est inclus, vous obtiendrez une instance de la variable statique. Ce n'est certainement pas l'intention. Le faire initialiser en dehors de la classe vous donne la possibilité de l'initialiser dans le fichier cpp.

3
RedX

La section 9.4.2, Membres de données statiques, de la norme C++ indique:

Si un membre de données static est de type énumération const intégral ou const, sa déclaration dans la définition de classe peut spécifier un const-initializer qui doit être une expression constante intégrale.

Par conséquent, il est possible que la valeur d'un membre de données statique soit incluse "dans la classe" (par laquelle je suppose que vous voulez dire dans la déclaration de la classe). Cependant, le type du membre de données statiques doit être un type d'énumération const intégral ou const. La raison pour laquelle les valeurs des membres de données statiques d'autres types ne peuvent pas être spécifiées dans la déclaration de classe est qu'une initialisation non triviale est probablement requise (c'est-à-dire qu'un constructeur doit s'exécuter).

Imaginez si les éléments suivants étaient légaux:

// my_class.hpp
#include <string>

class my_class
{
public:
  static std::string str = "static std::string";
//...

Chaque fichier objet correspondant aux fichiers CPP qui incluent cet en-tête aurait non seulement une copie de l'espace de stockage pour my_class::str (Composé de sizeof(std::string) octets), mais également une "section ctor" qui appelle le constructeur std::string prenant une chaîne C. Chaque copie de l'espace de stockage pour my_class::str Serait identifiée par une étiquette commune, de sorte qu'un éditeur de liens pourrait théoriquement fusionner toutes les copies de l'espace de stockage en une seule. Cependant, un éditeur de liens ne pourrait pas isoler toutes les copies du code constructeur dans les sections ctor des fichiers objets. Ce serait comme demander à l'éditeur de liens de supprimer tout le code pour initialiser str dans la compilation des éléments suivants:

std::map<std::string, std::string> map;
std::vector<int> vec;
std::string str = "test";
int c = 99;
my_class mc;
std::string str2 = "test2";

EDIT Il est instructif de regarder la sortie de l'assembleur de g ++ pour le code suivant:

// SO4547660.cpp
#include <string>

class my_class
{
public:
    static std::string str;
};

std::string my_class::str = "static std::string";

Le code d'assemblage peut être obtenu en exécutant:

g++ -S SO4547660.cpp

En parcourant le fichier SO4547660.s Généré par g ++, vous pouvez voir qu'il y a beaucoup de code pour un si petit fichier source.

__ZN8my_class3strE Est le libellé de l'espace de stockage pour my_class::str. Il existe également la source Assembly d'une fonction __static_initialization_and_destruction_0(int, int), qui a le libellé __Z41__static_initialization_and_destruction_0ii. Cette fonction est spéciale à g ++ mais sachez simplement que g ++ s'assurera qu'elle sera appelée avant l'exécution de tout code non initialiseur. Notez que l'implémentation de cette fonction appelle __ZNSsC1EPKcRKSaIcE. Il s'agit du symbole mutilé de std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&).

Pour revenir à l'exemple hypothétique ci-dessus et en utilisant ces détails, chaque fichier objet correspondant à un fichier CPP qui comprend my_class.hpp Aurait le libellé __ZN8my_class3strE Pour sizeof(std::string) octets ainsi que Code d'assembly pour appeler __ZNSsC1EPKcRKSaIcE Dans son implémentation de la fonction __static_initialization_and_destruction_0(int, int). L'éditeur de liens peut facilement fusionner toutes les occurrences de __ZN8my_class3strE, Mais il ne peut pas isoler le code qui appelle __ZNSsC1EPKcRKSaIcE Dans l'implémentation de __static_initialization_and_destruction_0(int, int) du fichier objet.

2
Daniel Trebbien

Je pense que la principale raison pour laquelle l'initialisation est effectuée en dehors du bloc class est de permettre l'initialisation avec les valeurs de retour des autres fonctions membres de la classe. Si vous souhaitez initialiser a::var Avec b::some_static_fn(), vous devez vous assurer que chaque fichier .cpp Qui comprend a.h Comprend b.h premier. Ce serait un gâchis, surtout quand (tôt ou tard) vous rencontrez une référence circulaire que vous ne pouviez résoudre qu'avec un interface autrement inutile. Le même problème est la principale raison d'avoir des implémentations de fonctions membres de classe dans un fichier .cpp Au lieu de tout mettre dans votre classe principale '.h.

Au moins avec les fonctions membres, vous avez la possibilité de les implémenter dans l'en-tête. Avec les variables, vous devez effectuer l'initialisation dans un fichier .cpp. Je ne suis pas tout à fait d'accord avec la limitation et je ne pense pas non plus qu'il y ait une bonne raison à cela.

0
martona