Forward déclarant une énumération en C ++

265

J'essaie de faire quelque chose comme ceci:

enum E;

void Foo(E e);

enum E {A, B, C};

que le compilateur rejette. J'ai jeté un rapide coup d'œil sur Google et le consensus semble être "vous ne pouvez pas le faire", mais je ne comprends pas pourquoi. Quelqu'un peut-il expliquer?

Clarification 2: je fais cela car j'ai des méthodes privées dans une classe qui prennent ladite énumération, et je ne veux pas que les valeurs de l'énumération soient exposées - donc, par exemple, je ne veux pas que quiconque sache que E est défini comme

enum E {
    FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X
}

car le projet X n'est pas quelque chose que je veux que mes utilisateurs connaissent.

Donc, je voulais transmettre déclarer l'énumération afin de pouvoir mettre les méthodes privées dans le fichier d'en-tête, déclarer l'énumération en interne dans le cpp et distribuer le fichier et l'en-tête de la bibliothèque construite aux personnes.

Quant au compilateur - c'est GCC.

szevvy
la source
Tant d'années dans ce domaine et StackOverflow m'a en quelque sorte attiré en arrière;) Comme suggestion post-mortem - ne faites pas cela en particulier dans le scénario que vous décrivez. Je préférerais définir une interface abstraite et l'exposer aux utilisateurs et conserver la définition d'énumération et tous les autres détails de l'implémentation avec l'implémentation interne que personne d'autre ne voit de mon côté me permettant de faire quoi que ce soit à tout moment et d'avoir un contrôle total sur le moment où les utilisateurs voient n'importe quoi.
RnR
Si vous lisez après la réponse acceptée, cela est tout à fait possible depuis C ++ 11.
fuzzyTew Il y a

Réponses:

217

La raison pour laquelle l'énum ne peut pas être déclarée en avant est que sans connaître les valeurs, le compilateur ne peut pas connaître le stockage requis pour la variable enum. Les compilateurs C ++ sont autorisés à spécifier l'espace de stockage réel en fonction de la taille nécessaire pour contenir toutes les valeurs spécifiées. Si tout ce qui est visible est la déclaration directe, l'unité de traduction ne peut pas savoir quelle taille de stockage aura été choisie - il pourrait s'agir d'un caractère ou d'un int, ou autre chose.


De la section 7.2.5 de la norme ISO C ++:

Le type sous-jacent d'une énumération est un type intégral qui peut représenter toutes les valeurs d'énumérateur définies dans l'énumération. Il est défini par l'implémentation quel type intégral est utilisé comme type sous-jacent pour une énumération, sauf que le type sous-jacent ne doit pas être plus grand que intsi la valeur d'un énumérateur ne peut pas tenir dans un intou unsigned int. Si la liste d'énumérateurs est vide, le type sous-jacent est comme si l'énumération avait un seul énumérateur avec la valeur 0. La valeur sizeof()appliquée à un type d'énumération, un objet de type énumération ou un énumérateur, est la valeur sizeof()appliquée à la type sous-jacent.

Étant donné que l' appelant à la fonction doit connaître la taille des paramètres pour configurer correctement la pile d'appels, le nombre d'énumérations dans une liste d'énumération doit être connu avant le prototype de la fonction.

Mise à jour: en C ++ 0X, une syntaxe de déclaration des types d'énumération a été proposée et acceptée. Vous pouvez voir la proposition à http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2764.pdf

KJAWolf
la source
29
-1. Votre raisonnement ne peut pas être correct - sinon, pourquoi êtes-vous autorisé à déclarer en amont «classe C»; puis déclarer un prototype de fonction qui prend ou retourne un C, avant de définir complètement C?
j_random_hacker
112
@j_random: Vous ne pouvez pas utiliser une classe avant qu'elle ne soit complètement définie - vous ne pouvez utiliser qu'un pointeur ou une référence à cette classe et c'est parce que leurs tailles et modes de fonctionnement ne dépendent pas de ce qu'est la classe.
RnR
27
La taille d'une référence ou d'un pointeur vers un objet de classe est définie par le compilateur, et indépendamment de la taille réelle de l'objet - c'est la taille des pointeurs et des références. L'énumération est un objet et sa taille est nécessaire pour que le compilateur accède au stockage correct.
KJAWolf
17
Logiquement, il serait capable de déclarer des pointeurs / références à des énumérations si nous avions des énumérations à déclaration directe, tout comme nous pouvons le faire avec les classes. C'est juste que vous ne traitez pas souvent les pointeurs vers les énumérations :)
Pavel Minaev
20
Je sais que cette discussion s'est terminée il y a longtemps, mais je dois m'aligner sur @j_random_hacker ici: le problème ici n'est pas le pointeur ou la référence aux types incomplets, mais l'utilisation de types incomplets dans les déclarations. Puisqu'il est légal de le faire struct S; void foo(S s);(notez que fooc'est seulement déclaré, non défini), alors il n'y a aucune raison pour que nous ne puissions pas faire enum E; void foo(E e);aussi bien. Dans les deux cas, la taille n'est pas nécessaire.
Luc Touraille
201

La déclaration anticipée des énumérations est possible depuis C ++ 11. Auparavant, la raison pour laquelle les types d'énumération ne pouvaient pas être déclarés avant était parce que la taille de l'énumération dépend de son contenu. Tant que la taille de l'énumération est spécifiée par l'application, elle peut être déclarée en avant:

enum Enum1;                   //Illegal in C++ and C++0x; no size is explicitly specified.
enum Enum2 : unsigned int;    //Legal in C++0x.
enum class Enum3;             //Legal in C++0x, because enum class declarations have a default type of "int".
enum class Enum4: unsigned int; //Legal C++0x.
enum Enum2 : unsigned short;  //Illegal in C++0x, because Enum2 was previously declared with a different type.
user119017
la source
1
Existe-t-il un support de compilation pour cette fonctionnalité? GCC 4.5 ne semble pas l'avoir :(
rubenvb
4
@rubenvb Tout comme Visual C ++ 11 (2012) blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx
knatten
Je cherchais enum32_t et avec votre réponse enum XXX: uint32_t {a, b, c};
fantastory
Je pensais que les énumérations étendues (classe enum) ont été implémentées en C ++ 11? Si oui, comment sont-ils alors légaux en C ++ 0X?
Terrabits
1
C ++ 0x était le nom de travail pour C ++ 11, @Terrabits, avant qu'il ne soit officiellement normalisé. La logique est que si une fonctionnalité est connue (ou très susceptible) d'être incluse dans une norme mise à jour, l'utilisation de cette fonctionnalité avant que la norme ne soit officiellement publiée a tendance à utiliser le nom de travail. (Par exemple, les compilateurs qui prenaient en charge les fonctionnalités C ++ 11 avant la normalisation officielle en 2011 avaient la prise en charge C ++ 0x, les compilateurs qui prenaient en charge les fonctionnalités C ++ 17 avant la normalisation officielle avaient la prise en charge C ++ 1z et les compilateurs qui prenaient en charge les fonctionnalités C ++ 20 en ce moment (2019) ont un support C ++ 2a.)
Justin Time - Rétablir Monica
79

J'ajoute une réponse à jour ici, compte tenu des développements récents.

Vous pouvez déclarer une énumération en aval dans C ++ 11, tant que vous déclarez son type de stockage en même temps. La syntaxe ressemble à ceci:

enum E : short;
void foo(E e);

....

enum E : short
{
    VALUE_1,
    VALUE_2,
    ....
}

En fait, si la fonction ne fait jamais référence aux valeurs de l'énumération, vous n'avez pas du tout besoin de la déclaration complète à ce stade.

Ceci est pris en charge par G ++ 4.6 et versions ultérieures ( -std=c++0xou -std=c++11dans les versions plus récentes). Visual C ++ 2013 prend en charge cela; dans les versions antérieures, il a une sorte de support non standard que je n'ai pas encore compris - j'ai trouvé une suggestion qu'une simple déclaration directe est légale, mais YMMV.

À M
la source
4
+1 car c'est la seule réponse qui mentionne que vous devez déclarer le type dans votre déclaration ainsi que votre définition.
turoni
Je crois que le support partiel au début de MSVC a été rétroporté à partir de C ++ / CLI en enum classtant qu'extension C ++ (avant C ++ 11 différent enum class), du moins si je me souviens bien. Le compilateur vous a permis de spécifier le type sous-jacent d'une énumération, mais ne prend pas en charge enum classou les énumérations déclarées vers l'avant, et vous a averti que la qualification d'un énumérateur avec la portée de l'énumération était une extension non standard. Je me souviens que cela fonctionnait à peu près de la même manière que la spécification du type sous-jacent en C ++ 11, sauf plus ennuyeux car vous deviez supprimer l'avertissement.
Justin Time - Réintègre Monica
30

La déclaration directe de choses en C ++ est très utile car elle accélère considérablement le temps de compilation . Vous pouvez déclarer avant plusieurs choses en C ++ , y compris: struct, class, function, etc ...

Mais pouvez-vous transmettre une déclaration enumen C ++?

Non tu ne peux pas.

Mais pourquoi ne pas le permettre? S'il était autorisé, vous pourriez définir votre enumtype dans votre fichier d'en-tête et vos enumvaleurs dans votre fichier source. On dirait que cela devrait être autorisé non?

Faux.

En C ++, il n'y a pas de type par défaut enumcomme en C # (int). En C ++ votre enumtype sera déterminé par le compilateur comme n'importe quel type qui s'adaptera à la plage de valeurs que vous avez pour votre enum.

Qu'est-ce que ça veut dire?

Cela signifie que le enumtype sous-jacent de votre ne peut pas être entièrement déterminé tant que vous n'avez pas toutes les valeurs du enumdéfini. Quel mans vous ne pouvez pas séparer la déclaration et la définition de votre enum. Et par conséquent, vous ne pouvez pas transmettre de déclaration enumen C ++.

La norme ISO C ++ S7.2.5:

Le type sous-jacent d'une énumération est un type intégral qui peut représenter toutes les valeurs d'énumérateur définies dans l'énumération. Il est défini par l'implémentation quel type intégral est utilisé comme type sous-jacent pour une énumération, sauf que le type sous-jacent ne doit pas être plus grand que intsi la valeur d'un énumérateur ne peut pas tenir dans un intou unsigned int. Si la liste d'énumérateurs est vide, le type sous-jacent est comme si l'énumération avait un seul énumérateur avec la valeur 0. La valeur sizeof()appliquée à un type d'énumération, un objet de type énumération ou un énumérateur, est la valeur sizeof()appliquée à la type sous-jacent.

Vous pouvez déterminer la taille d'un type énuméré en C ++ à l'aide de l' sizeofopérateur. La taille du type énuméré est la taille de son type sous-jacent. De cette façon, vous pouvez deviner quel type votre compilateur utilise pour votre enum.

Et si vous spécifiez le type de votre enumexplicitement comme ceci:

enum Color : char { Red=0, Green=1, Blue=2};
assert(sizeof Color == 1);

Pouvez-vous ensuite transmettre votre déclaration enum?

Mais pourquoi pas?

La spécification du type d'un enumne fait pas réellement partie de la norme C ++ actuelle. Il s'agit d'une extension VC ++. Il fera cependant partie de C ++ 0x.

La source

Brian R. Bondy
la source
15
Cette réponse est désormais dépassée depuis plusieurs années.
Tom
Le temps nous ridiculise tous. Votre commentaire est maintenant obsolète depuis plusieurs années; la réponse d'une décennie!
pjcard
14

[Ma réponse est fausse, mais je l'ai laissée ici parce que les commentaires sont utiles].

La déclaration en avant des énumérations n'est pas standard, car les pointeurs vers différents types d'énumérations ne sont pas garantis de la même taille. Le compilateur peut avoir besoin de voir la définition pour savoir quelles pointeurs de taille peuvent être utilisés avec ce type.

En pratique, au moins sur tous les compilateurs populaires, les pointeurs vers les énumérations ont une taille cohérente. La déclaration en avant des énumérations est fournie comme une extension de langage par Visual C ++, par exemple.

James Hopkin
la source
2
-1. Si votre raisonnement était correct, le même raisonnement impliquerait que les déclarations directes de types de classe ne pourraient pas être utilisées pour créer des pointeurs vers ces types - mais elles le peuvent.
j_random_hacker
6
+1. Le raisonnement est correct. Le cas spécifique est celui des plateformes où sizeof (char *)> sizeof (int *). Les deux peuvent être des types sous-jacents pour une énumération, selon la plage. Les classes n'ont pas de types sous-jacents, donc l'analogie est fausse.
MSalters
3
@MSalters: Exemple: "struct S {int x;};" Maintenant, sizeof (S *) doit être égal à la taille de tout autre pointeur vers structure, car C ++ permet à un tel pointeur d'être déclaré et utilisé avant la définition de S ...
j_random_hacker
1
@MSalters: ... Sur une plate-forme où sizeof (char *)> sizeof (int *), l'utilisation d'un tel pointeur "pleine taille" pour cette structure particulière peut être inefficace, mais cela simplifie considérablement le codage - et exactement la même chose pourrait et devrait être fait pour les types d'énumération.
j_random_hacker
4
les pointeurs vers les données et les pointeurs vers les fonctions peuvent être de tailles différentes, mais je suis assez sûr que les pointeurs de données doivent aller-retour (cast vers un autre type de pointeur de données, puis revenir à l'original, doivent toujours fonctionner), ce qui implique que tous les pointeurs de données ont la même taille.
Ben Voigt
7

Il n’existe en effet pas de déclaration d’énumération. Comme la définition d'une énumération ne contient aucun code pouvant dépendre d'un autre code utilisant l'énumération, il n'est généralement pas difficile de définir complètement l'énumération lorsque vous la déclarez pour la première fois.

Si la seule utilisation de votre énumération est par des fonctions de membre privé, vous pouvez implémenter l'encapsulation en ayant l'énumération elle-même en tant que membre privé de cette classe. L'énumération doit encore être entièrement définie au point de déclaration, c'est-à-dire dans la définition de classe. Cependant, ce n'est pas un problème plus important que d'y déclarer des fonctions de membre privé, et ce n'est pas une exposition pire des implémentations internes que cela.

Si vous avez besoin d'un degré de dissimulation plus profond pour les détails de votre implémentation, vous pouvez le diviser en une interface abstraite, composée uniquement de fonctions virtuelles pures, et d'une classe concrète complètement cachée implémentant (héritant) l'interface. La création d'instances de classe peut être gérée par une fabrique ou une fonction membre statique de l'interface. De cette façon, même le vrai nom de classe, sans parler de ses fonctions privées, ne sera pas exposé.

Alexey Feldgendler
la source
5

Il suffit de noter que la raison en fait est que la taille de l'énumération n'est pas encore connue après la déclaration préalable. Eh bien, vous utilisez la déclaration directe d'une structure pour pouvoir passer un pointeur ou faire référence à un objet à partir d'un endroit auquel il est également fait référence dans la définition de la structure déclarée directe.

La déclaration directe d'une énumération ne serait pas trop utile, car on souhaiterait pouvoir contourner la sous-valeur de l'énumération. Vous ne pouviez même pas avoir de pointeur dessus, car on m'a récemment dit que certaines plateformes utilisaient des pointeurs de taille différente pour char que pour int ou long. Tout dépend donc du contenu de l'énumération.

La norme C ++ actuelle interdit explicitement de faire quelque chose comme

enum X;

(en 7.1.5.3/1). Mais la prochaine norme C ++ en raison de l'année prochaine permet ce qui suit, ce qui m'a convaincu que le problème est en fait lié au type sous-jacent:

enum X : int;

Il est connu comme une déclaration d'énumération «opaque». Vous pouvez même utiliser X par valeur dans le code suivant. Et ses énumérateurs peuvent être définis ultérieurement dans une redéclaration ultérieure de l'énumération. Voir 7.2dans le projet de travail actuel.

Johannes Schaub - litb
la source
4

Je le ferais de cette façon:

[dans l'en-tête public]

typedef unsigned long E;

void Foo(E e);

[dans l'en-tête interne]

enum Econtent { FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X,
  FORCE_32BIT = 0xFFFFFFFF };

En ajoutant FORCE_32BIT, nous nous assurons qu'Econtent se compile sur une longue, il est donc interchangeable avec E.

Laurie Cheers
la source
1
Bien sûr, cela signifie que (A) les types d'E et d'Econtent diffèrent, et (B) sur les systèmes LP64, sizeof (E) = 2 * sizeof (EContent). Correction triviale: ULONG_MAX, plus facile à lire également.
MSalters
2

Si vous ne voulez vraiment pas que votre énumération apparaisse dans votre fichier d'en-tête ET assurez-vous qu'elle n'est utilisée que par des méthodes privées, alors une solution peut être d'aller avec le principe de pimpl.

C'est une technique qui garantit de cacher les internes de classe dans les en-têtes en déclarant simplement:

class A 
{
public:
    ...
private:
    void* pImpl;
};

Ensuite, dans votre fichier d'implémentation (cpp), vous déclarez une classe qui sera la représentation des internes.

class AImpl
{
public:
    AImpl(A* pThis): m_pThis(pThis) {}

    ... all private methods here ...
private:
    A* m_pThis;
};

Vous devez créer dynamiquement l'implémentation dans le constructeur de classe et la supprimer dans le destructeur et lors de l'implémentation de la méthode publique, vous devez utiliser:

((AImpl*)pImpl)->PrivateMethod();

Il y a des avantages à utiliser pimpl, l'un est qu'il dissocie votre en-tête de classe de son implémentation, pas besoin de recompiler d'autres classes lors du changement d'implémentation d'une classe. Un autre est que cela accélère votre temps de compilation car vos en-têtes sont si simples.

Mais c'est difficile à utiliser, vous devriez donc vraiment vous demander si le simple fait de déclarer votre énumération privée dans l'en-tête est un problème.

Vincent Robert
la source
3
struct AImpl; struct A {privé: AImpl * pImpl; };
2

Vous pouvez encapsuler l'énumération dans une structure, en ajoutant certains constructeurs et conversions de type, et déclarer la structure à la place.

#define ENUM_CLASS(NAME, TYPE, VALUES...) \
struct NAME { \
    enum e { VALUES }; \
    explicit NAME(TYPE v) : val(v) {} \
    NAME(e v) : val(v) {} \
    operator e() const { return e(val); } \
    private:\
        TYPE val; \
}

Cela semble fonctionner: http://ideone.com/TYtP2

Leszek Swirski
la source
1

Semble qu'il ne peut pas être déclaré à l'avance dans GCC!

Discussion intéressante ici

prakash
la source
1

Il y a une certaine dissidence depuis que cela a été heurté (en quelque sorte), alors voici quelques bits pertinents de la norme. La recherche montre que la norme ne définit pas vraiment la déclaration directe, ni n'indique explicitement que les énumérations peuvent ou ne peuvent pas être déclarées avant.

Tout d'abord, à partir de dcl.enum, section 7.2:

Le type sous-jacent d'une énumération est un type intégral qui peut représenter toutes les valeurs d'énumérateur définies dans l'énumération. Il est défini par l'implémentation quel type intégral est utilisé comme type sous-jacent pour une énumération, sauf que le type sous-jacent ne doit pas être supérieur à int sauf si la valeur d'un énumérateur ne peut pas tenir dans un int ou un int non signé. Si la liste d'énumérateurs est vide, le type sous-jacent est comme si l'énumération avait un seul énumérateur avec la valeur 0. La valeur de sizeof () appliquée à un type d'énumération, un objet de type énumération ou un énumérateur, est la valeur de sizeof () appliqué au type sous-jacent.

Ainsi, le type sous-jacent d'une énumération est défini par l'implémentation, avec une restriction mineure.

Ensuite, nous passons à la section sur les "types incomplets" (3.9), qui est à peu près aussi proche que possible de toute norme sur les déclarations à terme:

Une classe déclarée mais non définie, ou un tableau de taille inconnue ou de type d'élément incomplet, est un type d'objet défini de manière incomplète.

Un type de classe (tel que "classe X") peut être incomplet à un moment donné dans une unité de traduction et terminé plus tard; le type "classe X" est du même type aux deux points. Le type déclaré d'un objet tableau peut être un tableau de type classe incomplet et donc incomplet; si le type de classe est terminé plus tard dans l'unité de traduction, le type de tableau devient complet; le type de tableau à ces deux points est le même type. Le type déclaré d'un objet tableau peut être un tableau de taille inconnue et donc être incomplet à un moment donné dans une unité de traduction et se terminer plus tard; les types de tableau en ces deux points ("tableau de borne inconnue de T" et "tableau de N T") sont des types différents. Le type d'un pointeur vers un tableau de taille inconnue, ou d'un type défini par une déclaration typedef comme étant un tableau de taille inconnue,

Donc, là, la norme énonçait à peu près les types qui peuvent être déclarés. Enum n'était pas là, donc les auteurs de compilateurs considèrent généralement la déclaration avant comme interdite par la norme en raison de la taille variable de son type sous-jacent.

Cela a également du sens. Les énumérations sont généralement référencées dans des situations par valeur, et le compilateur aurait en effet besoin de connaître la taille de stockage dans ces situations. Étant donné que la taille de stockage est définie par l'implémentation, de nombreux compilateurs peuvent simplement choisir d'utiliser des valeurs de 32 bits pour le type sous-jacent de chaque énumération, auquel cas il devient possible de les déclarer en avant. Une expérience intéressante pourrait être d'essayer de déclarer une énumération dans Visual Studio, puis de la forcer à utiliser un type sous-jacent supérieur à sizeof (int) comme expliqué ci-dessus pour voir ce qui se passe.

Dan Olson
la source
notez qu'il interdit explicitement "enum foo;" dans 7.1.5.3/1 (mais comme pour tout, tant que le compilateur avertit, il peut toujours compiler un tel code, bien sûr)
Johannes Schaub - litb
Merci de l'avoir signalé, c'est un paragraphe vraiment ésotérique et cela pourrait me prendre une semaine pour l'analyser. Mais c'est bon de savoir que c'est là.
Dan Olson
pas de soucis.Certains paragraphes standard sont vraiment étranges :) eh bien, un spécificateur de type élaboré est quelque chose où vous spécifiez un type, mais spécifiez également quelque chose de plus pour le rendre sans ambiguïté. par exemple "struct X" au lieu de "X", ou "enum Y" au lieu de seulement "Y". Vous en avez besoin pour affirmer que quelque chose est vraiment un type.
Johannes Schaub - litb
vous pouvez donc l'utiliser comme ceci: "class X * foo;" si X n'était pas encore déclaré. ou "typename X :: foo" dans un modèle de désambiguïsation. ou "class link obj;" s'il existe une fonction "lien" dans la même portée qui masquerait la classe portant le même nom.
Johannes Schaub - litb
dans 3.4.4, il est dit qu'ils sont utilisés si un nom non-type cache un nom de type. c'est là qu'ils sont le plus souvent utilisés, à part déclarer en avant comme "classe X;" (ici il s'agit de la seule constitution d'une déclaration). il en parle ici dans des non-modèles. cependant, 14.6 / 3 en répertorie une utilisation dans les modèles.
Johannes Schaub - litb
1

Pour VC, voici le test sur la déclaration directe et la spécification du type sous-jacent:

  1. le code suivant est compilé ok.
    typedef int myint;
    enum T;
    void foo (T * tp)
    {
        * tp = (T) 0x12345678;
    }
    énumération T: char
    {
        UNE
    };

Mais j'ai reçu l'avertissement pour / W4 (/ W3 n'encourra pas cet avertissement)

avertissement C4480: extension non standard utilisée: spécification du type sous-jacent pour l'énumération 'T'

  1. VC (Microsoft (R) 32-bit C / C ++ Optimizing Compiler Version 15.00.30729.01 pour 80x86) semble bogué dans le cas ci-dessus:

    • en voyant l'énumération T; VC suppose que le type enum T utilise int 4 octets par défaut comme type sous-jacent, donc le code d'assembly généré est:
    ? foo @@ YAXPAW4T @@@ Z PROC; foo
    ; Fichier e: \ work \ c_cpp \ cpp_snippet.cpp
    ; Ligne 13
        pousser ebp
        mov ebp, esp
    ; Ligne 14
        mov eax, DWORD PTR _tp $ [ebp]
        mov DWORD PTR [eax], 305419896; 12345678H
    ; Ligne 15
        pop ebp
        ret 0
    ? foo @@ YAXPAW4T @@@ Z ENDP; foo

Le code assembleur ci-dessus est extrait directement de /Fatest.asm, pas ma supposition personnelle. Voyez-vous le mov DWORD PTR [eax], 305419896; Ligne 12345678H?

l'extrait de code suivant le prouve:

    int main (int argc, char * argv)
    {
        syndicat {
            char ca [4];
            T t;
        }une;
        a.ca [0] = a.ca [1] = a. [ca [2] = a.ca [3] = 1;
        foo (& a.t);
        printf ("% # x,% # x,% # x,% # x \ n", a.ca [0], a.ca [1], a.ca [2], a.ca [3]) ;
        retourner 0;
    }

le résultat est: 0x78, 0x56, 0x34, 0x12

  • après avoir supprimé la déclaration avant de l'énum T et déplacer la définition de la fonction foo après la définition de l'énum T: le résultat est OK:

l'instruction clé ci-dessus devient:

mov BYTE PTR [eax], 120; 00000078H

le résultat final est: 0x78, 0x1, 0x1, 0x1

Notez que la valeur n'est pas écrasée

Par conséquent, l'utilisation de la déclaration en avant d'énumération dans VC est considérée comme nuisible.

BTW, pour ne pas surprendre, la syntaxe de déclaration du type sous-jacent est la même qu'en C #. Dans la pratique, j'ai trouvé qu'il valait la peine d'économiser 3 octets en spécifiant le type sous-jacent comme char lorsque vous parlez au système intégré, qui est limité en mémoire.

zhaorufei
la source
1

Dans mes projets, j'ai adopté la technique d' énumération liée à l' espace de noms pour gérer les enums des composants hérités et tiers. Voici un exemple:

forward.h:

namespace type
{
    class legacy_type;
    typedef const legacy_type& type;
}

enum.h:

// May be defined here or pulled in via #include.
namespace legacy
{
    enum evil { x , y, z };
}


namespace type
{
    using legacy::evil;

    class legacy_type
    {
    public:
        legacy_type(evil e)
            : e_(e)
        {}

        operator evil() const
        {
            return e_;
        }

    private:
        evil e_;
    };
}

foo.h:

#include "forward.h"

class foo
{
public:
    void f(type::type t);
};

foo.cc:

#include "foo.h"

#include <iostream>
#include "enum.h"

void foo::f(type::type t)
{
    switch (t)
    {
        case legacy::x:
            std::cout << "x" << std::endl;
            break;
        case legacy::y:
            std::cout << "y" << std::endl;
            break;
        case legacy::z:
            std::cout << "z" << std::endl;
            break;
        default:
            std::cout << "default" << std::endl;
    }
}

main.cc:

#include "foo.h"
#include "enum.h"

int main()
{
    foo fu;
    fu.f(legacy::x);

    return 0;
}

Notez que l'en- foo.htête n'a rien à savoir legacy::evil. Seuls les fichiers qui utilisent le type hérité legacy::evil(ici: main.cc) doivent être inclus enum.h.

mavam
la source
0

Ma solution à votre problème serait de:

1 - utilisez int au lieu d'enums: Déclarez vos entrées dans un espace de noms anonyme dans votre fichier CPP (pas dans l'en-tête):

namespace
{
   const int FUNCTIONALITY_NORMAL = 0 ;
   const int FUNCTIONALITY_RESTRICTED = 1 ;
   const int FUNCTIONALITY_FOR_PROJECT_X = 2 ;
}

Comme vos méthodes sont privées, personne ne gâchera les données. Vous pouvez même aller plus loin pour tester si quelqu'un vous envoie des données invalides:

namespace
{
   const int FUNCTIONALITY_begin = 0 ;
   const int FUNCTIONALITY_NORMAL = 0 ;
   const int FUNCTIONALITY_RESTRICTED = 1 ;
   const int FUNCTIONALITY_FOR_PROJECT_X = 2 ;
   const int FUNCTIONALITY_end = 3 ;

   bool isFunctionalityCorrect(int i)
   {
      return (i >= FUNCTIONALITY_begin) && (i < FUNCTIONALITY_end) ;
   }
}

2: créer une classe complète avec des instanciations const limitées, comme fait en Java. Déclarez la classe, puis définissez-la dans le fichier CPP, et instanciez uniquement les valeurs de type énumération. J'ai fait quelque chose comme ça en C ++, et le résultat n'était pas aussi satisfaisant que souhaité, car il avait besoin de code pour simuler une énumération (construction de copie, opérateur =, etc.).

3: Comme proposé précédemment, utilisez l'énumération déclarée privée. Malgré le fait qu'un utilisateur verra sa définition complète, il ne pourra pas l'utiliser, ni utiliser les méthodes privées. Ainsi, vous pourrez généralement modifier l'énumération et le contenu des méthodes existantes sans avoir besoin de recompiler le code à l'aide de votre classe.

Je suppose que ce serait la solution 3 ou 1.

paercebal
la source
-1

Étant donné que l'énumération peut être une taille intégrale de taille variable (le compilateur décide de la taille d'une énumération donnée), le pointeur vers l'énumération peut également avoir une taille variable, car il s'agit d'un type intégral (les caractères ont des pointeurs d'une taille différente sur certaines plates-formes). par exemple).

Ainsi, le compilateur ne peut même pas vous laisser déclarer en avant l'énumération et utiliser un pointeur vers celle-ci, car même là, il a besoin de la taille de l'énumération.

Carl Seleborg
la source
-1

Vous définissez une énumération pour restreindre les valeurs possibles des éléments du type à un ensemble limité. Cette restriction doit être appliquée au moment de la compilation.

Lorsque vous déclarez par la suite que vous utiliserez un «ensemble limité» plus tard, cela n'ajoute aucune valeur: le code suivant doit connaître les valeurs possibles pour en bénéficier.

Bien que le compilateur soit préoccupé par la taille du type énuméré, l' intention de l'énumération est perdue lorsque vous la déclarez.

xtofl
la source
1
Non, le code suivant n'a pas besoin de connaître les valeurs pour que cela soit utile - en particulier, si le code suivant est simplement un prototype de fonction prenant ou renvoyant des paramètres d'énumération, la taille du type n'est pas importante. L'utilisation de la déclaration directe ici peut supprimer les dépendances de construction, ce qui accélère la compilation.
j_random_hacker
Vous avez raison. L'intention n'est pas d'obéir aux valeurs, mais au type. Résolu avec les types 0x Enum.
xtofl