class A {
static int foo () {} // ok
static int x; // <--- needed to be defined separately in .cpp file
};
Je ne vois pas la nécessité d'avoir A::x
défini séparément dans un fichier .cpp (ou le même fichier pour les modèles). Pourquoi ne peut pas être A::x
déclaré et défini en même temps?
At-il été interdit pour des raisons historiques?
Ma question principale est la suivante: cela affectera-t-il une fonctionnalité si static
les membres de données étaient déclarés / définis en même temps (comme en Java )?
c++
data
language-features
grammar
static-access
iammilind
la source
la source
inline static int x[] = {1, 2, 3};
. Voir fr.cppreference.com/w/cpp/language/static#Static_data_membersRéponses:
Je pense que la limitation que vous avez envisagée n’est pas liée à la sémantique (pourquoi changer quelque chose si l’initialisation est définie dans le même fichier?), Mais plutôt au modèle de compilation C ++ qui, pour des raisons de compatibilité ascendante, ne peut pas être changé facilement soit devenir trop complexe (prendre en charge simultanément un nouveau modèle de compilation et le modèle existant), soit ne pas permettre de compiler le code existant (en introduisant un nouveau modèle de compilation et en supprimant le modèle existant).
Le modèle de compilation C ++ provient de celui de C, dans lequel vous importez des déclarations dans un fichier source en incluant des fichiers (en-tête). De cette manière, le compilateur voit exactement un gros fichier source, contenant tous les fichiers inclus et tous les fichiers inclus à partir de ces fichiers, de manière récursive. Cela présente un gros avantage pour OMI, à savoir que cela facilite la mise en œuvre du compilateur. Bien sûr, vous pouvez écrire n’importe quoi dans les fichiers inclus, c’est-à-dire à la fois des déclarations et des définitions. Il est recommandé de placer les déclarations dans les fichiers d’en-tête et les définitions dans les fichiers .c ou .cpp.
D'autre part, il est possible d'avoir un modèle de compilation dans lequel le compilateur sait très bien s'il importe la déclaration d'un symbole global défini dans un autre module ou s'il compile la définition d'un symbole global fourni par le module actuel . Ce n'est que dans ce dernier cas que le compilateur doit mettre ce symbole (par exemple une variable) dans le fichier objet actuel.
Par exemple, dans GNU Pascal, vous pouvez écrire une unité
a
dans un fichiera.pas
comme celui-ci:où la variable globale est déclarée et initialisée dans le même fichier source.
Ensuite, vous pouvez avoir différentes unités qui importent a et utilisent la variable globale
MyStaticVariable
, par exemple une unité b (b.pas
):et une unité c (
c.pas
):Enfin, vous pouvez utiliser les unités b et c dans un programme principal
m.pas
:Vous pouvez compiler ces fichiers séparément:
et ensuite produire un exécutable avec:
et lancez-le:
L'astuce ici est que lorsque le compilateur voit une directive uses dans un module de programme (par exemple, utilise a dans b.pas), il n'inclut pas le fichier .pas correspondant, mais recherche un fichier .gpi, c'est-à-dire un fichier pré-compilé. fichier d'interface (voir la documentation ). Ces
.gpi
fichiers sont générés par le compilateur avec les.o
fichiers lors de la compilation de chaque module. Le symbole globalMyStaticVariable
n’est donc défini qu’une fois dans le fichier objeta.o
.Java fonctionne de la même manière: lorsque le compilateur importe une classe A dans la classe B, il examine le fichier de classe pour A et n’a pas besoin de ce fichier
A.java
. Ainsi, toutes les définitions et initialisations de la classe A peuvent être placées dans un fichier source.Pour revenir à C ++, la raison pour laquelle vous devez définir des membres de données statiques dans un fichier séparé est davantage liée au modèle de compilation C ++ qu'aux limitations imposées par l'éditeur de liens ou d'autres outils utilisés par le compilateur. En C ++, importer des symboles signifie construire leur déclaration dans l'unité de compilation en cours. Ceci est très important, entre autres choses, à cause de la manière dont les modèles sont compilés. Mais cela implique que vous ne pouvez / ne devez définir aucun symbole global (fonctions, variables, méthodes, membres de données statiques) dans un fichier inclus, sinon ces symboles pourraient être définis de manière multiple dans les fichiers d'objet compilés.
la source
Les membres statiques étant partagés entre TOUTES les instances d'une classe, ils doivent être définis dans un seul et même emplacement. En réalité, ce sont des variables globales avec certaines restrictions d'accès.
Si vous essayez de les définir dans l'en-tête, ils seront définis dans chaque module qui inclut cet en-tête, et vous obtiendrez des erreurs lors de la liaison lors de la recherche de toutes les définitions en double.
Oui, il s’agit au moins en partie d’une question historique qui remonte à l’avant; un compilateur pourrait être écrit pour créer une sorte de "static_members_of_everything.cpp" caché et un lien vers cela. Cependant, cela éliminerait la compatibilité en amont et ne présenterait aucun avantage réel.
la source
static
variables sont déclarées / définies au même endroit (comme Java), qu'est-ce qui peut mal se passer?static
membrestemplate
? Ils sont autorisés dans tous les fichiers d'en-tête car ils doivent être visibles. Je ne conteste pas cette réponse, mais elle ne correspond pas non plus à ma question.La raison probable en est que le langage C ++ peut être implémenté dans des environnements où le fichier objet et le modèle de liaison ne prennent pas en charge la fusion de plusieurs définitions à partir de plusieurs fichiers objet.
Une déclaration de classe (appelée déclaration pour de bonnes raisons) est extraite dans plusieurs unités de traduction. Si la déclaration contenait des définitions pour les variables statiques, vous vous retrouveriez avec plusieurs définitions dans plusieurs unités de traduction (rappelez-vous, ces noms ont un lien externe.)
Cette situation est possible, mais requiert que l'éditeur de liens gère plusieurs définitions sans se plaindre.
(Notez que ceci est en conflit avec la règle de définition unique, à moins que cela ne puisse être fait en fonction du type de symbole ou du type de section dans lequel il est placé.)
la source
Il y a une grande différence entre C ++ et Java.
Java fonctionne sur sa propre machine virtuelle qui crée tout dans son propre environnement d'exécution. Si une définition apparaît plus d'une fois, agira simplement sur le même objet que l'environnement d'exécution connaît ultimatement.
En C ++, il n’existe pas de "propriétaire ultime de connaissances": C ++, C, Fortran Pascal, etc. sont tous "traducteurs" d’un code source (fichier CPP) dans un format intermédiaire (le fichier OBJ ou le fichier ".o", l'OS) où les instructions sont traduites en instructions machine et les noms deviennent des adresses indirectes médiées par une table de symboles.
Un programme n’est pas créé par le compilateur, mais par un autre programme (le "lieur"), qui relie tous les OBJ-s ensemble (quelle que soit leur langue) en redirigeant toutes les adresses dirigées vers des symboles, vers leur définition efficace.
De par le mode de fonctionnement de l'éditeur de liens, une définition (ce qui crée l'espace physique d'une variable) doit être unique.
Notez que C ++ ne lie pas par lui-même et que l'éditeur de liens n'est pas publié par les spécifications C ++: il existe en raison de la structure des modules du système d'exploitation (généralement en C et ASM). C ++ doit l'utiliser tel quel.
Maintenant: un fichier d'en-tête est quelque chose qui doit être "collé" dans plusieurs fichiers CPP. Chaque fichier CPP est traduit indépendamment des autres. Un compilateur traduisant différents fichiers CPP, recevant tous dans une même définition, placera le " code de création " de l'objet défini dans tous les OBJ résultants.
Le compilateur ne sait pas (et ne saura jamais) si tous ces OBJ seront jamais utilisés ensemble pour former un seul programme ou séparément pour former différents programmes indépendants.
L'éditeur de liens ne sait pas comment et pourquoi les définitions existent et d'où elles viennent (il ne sait même pas à propos de C ++: chaque "langage statique" peut produire des définitions et des références à lier). Il sait simplement qu'il y a des références à un "symbole" donné qui est "défini" à une adresse résultante donnée.
S'il existe plusieurs définitions (ne confondez pas les définitions avec les références) pour un symbole donné, l'éditeur de liens n'a aucune connaissance (ne tenant pas compte de la langue) de ce qu'il faut en faire.
C'est comme si vous fusionniez plusieurs villes pour former une grande ville: si vous vous retrouvez avec deux " Time square " et si plusieurs personnes venant de l'extérieur demandent d'aller à " Time square ", vous ne pouvez pas choisir uniquement sur des bases techniques. (sans aucune connaissance de la politique qui a assigné ces noms et sera chargé de les gérer) dans quel endroit exact les envoyer.
la source
C'est obligatoire car sinon le compilateur ne sait pas où placer la variable. Chaque fichier cpp est compilé individuellement et ne connaît pas l’autre. L'éditeur de liens résout les variables, les fonctions, etc. Personnellement, je ne vois pas quelle est la différence entre les membres vtable et static (nous n'avons pas à choisir le fichier dans lequel la vtable est définie).
J'assume surtout qu'il est plus facile pour les auteurs de compilateur de le mettre en œuvre de cette façon. Les variables statiques en dehors de la classe / structure existent et peut-être soit pour des raisons de cohérence, soit parce que ce serait "plus facile à mettre en oeuvre" pour les rédacteurs de compilateur, ils ont défini cette restriction dans les normes.
la source
Je pense avoir trouvé la raison. Définir une
static
variable dans un espace séparé permet de l'initialiser à n'importe quelle valeur. S'il n'est pas initialisé, la valeur par défaut est 0.Avant C ++ 11, l'initialisation dans la classe n'était pas autorisée en C ++. Donc on ne peut pas écrire comme:
Donc maintenant, pour initialiser la variable, il faut l’écrire en dehors de la classe:
Comme indiqué dans d'autres réponses également,
int X::i
est maintenant un global et la déclaration de globale dans de nombreux fichiers provoque plusieurs erreurs de liaison de symboles.Ainsi, il faut déclarer une
static
variable de classe dans une unité de traduction distincte. Cependant, on peut toujours affirmer que la méthode suivante doit indiquer au compilateur de ne pas créer plusieurs symbolesla source
A :: x est juste une variable globale mais l'espace de nom est attribué à A et avec des restrictions d'accès.
Quelqu'un doit encore le déclarer, comme toute autre variable globale, et cela peut même être fait dans un projet lié statiquement au projet contenant le reste du code A.
J'appellerais cela du mauvais design, mais il y a quelques fonctionnalités que vous pouvez exploiter de cette façon:
l'ordre d'appel du constructeur ... Pas important pour un int, mais pour un membre plus complexe pouvant accéder à d'autres variables statiques ou globales, cela peut être critique.
l'initialiseur statique - vous pouvez laisser un client décider de ce à quoi A :: x doit être initialisé.
dans c ++ et c, étant donné que vous avez un accès complet à la mémoire via des pointeurs, l'emplacement physique des variables est significatif. Il y a des choses très méchantes que vous pouvez exploiter en fonction de l'emplacement d'une variable dans un objet de lien.
Je doute que ce soit "pourquoi" cette situation s'est produite. Il s’agit probablement d’une évolution de C en C ++ et d’un problème de compatibilité ascendante qui vous empêche de changer de langage maintenant.
la source