48 votes

Variable membre statique C ++ et son initialisation


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

Je pense qu'avoir l'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 statique const.

37voto

John Dibling Points 56814

Fondamentalement, c'est parce que les membres statiques doivent être définis dans exactement une unité de traduction, afin de ne pas enfreindre l' Une Définition de la Règle. Si la langue ont été à permettre à quelque chose comme:

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

ensuite, name devrait être défini dans chaque unité de traduction qu' #includes ce fichier d'en-tête.

C++ permet de définir l'intégrale des membres statiques au sein de la déclaration, mais vous devez toujours inclure une définition au sein d'une seule unité de traduction, mais c'est juste un raccourci, ou de sucre syntaxique. Donc, il est permis de:

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

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

fichier: gizmo.cpp

#include "gizmo.h"

const int Gizmo::count;

12voto

AndreyT Points 139512

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

Comme vous le savez, chaque objet externe utilisé dans un programme C++ doit être défini une fois et une seule fois dans une seule unité de traduction. Permettre, dans la classe des initialiseurs d'objets statiques serait immédiatement aller à l'encontre de la présente convention: les initialiseurs serait aller dans les fichiers d'en-tête (où les définitions de classe résidez habituellement) et donc de générer de multiples définitions de la même objet statique (un pour chaque unité de traduction qui comprend le fichier d'en-tête). C'est, bien sûr, inacceptable. Pour cette raison, la déclaration de l'approche statique de la classe de membres de la gauche est parfaitement "traditionnel": il vous suffit de le déclarer dans le fichier d'en-tête (c'est à dire pas d'initialiseur de permis), et puis vous définissez dans une unité de traduction de votre choix (éventuellement avec un initialiseur).

Une exception à cette règle a été établie pour const classe statique des membres de l'intégrale ou types enum, parce que ces entrées peuvent Intégrale des Expressions Constantes (Ciem). L'idée principale de l'Irss est qu'ils sont évalués au moment de la compilation, et donc ne pas dépendre sur les définitions des objets concernés. C'est pourquoi cette exception a été possible pour les intégrales ou les types d'énumérations. Mais pour d'autres types, il serait tout simplement en contradiction avec la déclaration de base/définition des principes de C++.

3voto

RedX Points 7449

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

2voto

Daniel Trebbien Points 18089

Section 9.4.2, les données membres Statiques, de la norme C++ états:

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

Par conséquent, il est possible que la valeur d'une donnée membre statique pour être inclus "dans la classe" (par qui je présume que tu veux dire dans la déclaration de la classe). Cependant, le type de la donnée membre statique doit être un const intégrale ou const de type énumération. La raison pour laquelle les valeurs des données membres statiques d'autres types de ne peut pas être spécifié dans la déclaration de classe, c'est que non-trivial d'initialisation est probablement nécessaire (c'est à dire, un constructeur a besoin pour fonctionner).

Imaginez si les conditions suivantes étaient légales:

// my_class.hpp
#include <string>

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

Chaque fichier de l'objet correspondant au RPC fichiers qui incluent cet en-tête ne serait pas seulement d'avoir une copie de l'espace de stockage pour my_class::str (composé de l' sizeof(std::string) octets), mais aussi un "ctor section" qui appelle l' std::string constructeur prenant un C-string. Chaque copie de l'espace de stockage pour my_class::str seront identifiés par un label commun, donc un linker pourrait théoriquement fusionner toutes les copies de l'espace de stockage en un seul. Cependant, un éditeur de liens ne seraient pas en mesure d'isoler toutes les copies du constructeur de code dans les fichiers de l'objet' ctor sections. Ce serait comme demander à l'éditeur de liens pour 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 l'assembleur de sortie 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";

L'assemblée de code peut être obtenue en exécutant:

g++ -S SO4547660.cpp

En regardant à travers l' SO4547660.s le fichier que g++ génère, vous pouvez voir qu'il y a beaucoup de code pour un si petit fichier source.

__ZN8my_class3strE est l'étiquette de l'espace de stockage pour my_class::str. Il y a aussi le montage d'une source de __static_initialization_and_destruction_0(int, int) la fonction, qui a l'étiquette __Z41__static_initialization_and_destruction_0ii. Cette fonction est spécifique à g++, mais sachez juste que g++ sera assurez-vous qu'elle est appelée avant tout non-initialiseur de code est exécuté. Notez que la mise en œuvre de cette fonction appelle __ZNSsC1EPKcRKSaIcE. C'est la déformation symbole std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&).

Revenons à l'exemple hypothétique ci-dessus et à l'aide de ces informations, chaque fichier de l'objet correspondant à un fichier CPP qui inclut my_class.hpp aurait l'étiquette __ZN8my_class3strE pour sizeof(std::string) octets ainsi que de l'assemblée du code pour appeler l' __ZNSsC1EPKcRKSaIcE dans sa mise en œuvre de l' __static_initialization_and_destruction_0(int, int) fonction. 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 le fichier de l'objet de la mise en œuvre de l' __static_initialization_and_destruction_0(int, int).

0voto

martona Points 2873

Je pense que la principale raison d'avoir de l'initialisation effectuée à l'extérieur de l' class bloc est de permettre l'initialisation avec les valeurs de retour des autres fonctions de membre de classe. Si vous voulais initialiser a::var avec b::some_static_fn() vous auriez besoin de faire en sorte que chaque .cpp le fichier qui inclut a.h inclut b.h première. Ce serait un gâchis, surtout quand (tôt ou tard) vous avez une référence circulaire que vous ne pouvez les résoudre avec un autrement inutiles interface. La même question est la principale raison de fonction membre de classe mises en œuvre dans un .cpp le fichier au lieu de tout mettre dans votre classe principale' .h.

Au moins avec les fonctions de membre vous avez la possibilité de les mettre en œuvre dans l'en-tête. Avec les variables, vous devez effectuer l'initialisation dans un .fichier cpp. Je n'ai pas tout à fait d'accord avec la limitation, et je ne pense pas qu'il y a une bonne raison pour qu'il soit.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X