web-dev-qa-db-fra.com

L'utilisation d'énums anonymes

Quel est le but des déclarations enum anonymes telles que:

enum { color = 1 };

Pourquoi ne pas simplement déclarer int color = 1?

59
user707549

Les énums ne prennent pas de place et sont immuables. 

Si vous utilisiez const int color = 1;, vous résoudriez le problème de mutabilité, mais si quelqu'un prenait l'adresse color (const int* p = &color;), un espace lui serait attribué. Ce n'est peut-être pas grave, mais à moins que vous vouliez explicitement que les gens puissent prendre l'adresse de color, vous pourriez aussi bien l'empêcher.

De plus, lors de la déclaration d'un champ constant dans une classe, il devra être static const (pas vrai pour le C++ moderne) et tous les compilateurs ne prennent pas en charge l'initialisation en ligne des membres const stat.


Avertissement: Cette réponse ne doit pas être considérée comme un conseil d'utiliser enum pour toutes les constantes numériques. Vous devriez faire ce que vous (ou vos vachers) pensez être plus lisible. La réponse n'énumère que quelques raisons pour lesquelles pourrait préférer utiliser un enum.

54
Motti

C'est ce que l'on appelle une astuce d'énum pour déclarant une constante entière à la compilation . Son avantage est qu’il garantit qu’aucune variable n’est instanciée et qu’il n’ya donc aucune surcharge d’exécution. La plupart des compilateurs n'introduisent de toute façon pas de surcharge avec les constantes de nombre entier.

80
sharptooth

S'il s'agit d'un ancien code, alors enum aurait peut-être été utilisé pour le "piratage enum".

Vous pouvez en apprendre plus sur le "hack enum", par exemple, dans ce lien: enum hack

6
Eran Zimmerman
(1) int color = 1;

color est modifiable (accidentellement).

(2) enum { color = 1 };

color ne peut pas être changé.

L’autre option pour enum est,

const int color = 1;  // 'color' is unmutable

enum et const int offrent exactement le même concept; c'est une question de choix. En ce qui concerne la croyance populaire selon laquelle enums économiser de l'espace, IMO il n'y a pas de contrainte de mémoire liée à cela, les compilateurs sont assez intelligents pour optimiser const int lorsque cela est nécessaire.

[Remarque: si quelqu'un essaie d'utiliser const_cast<> sur un const int; il en résultera un comportement indéfini (ce qui est mauvais). Cependant, la même chose n'est pas possible pour enum. Donc mon préféré est enum]

4
iammilind

Une utilisation de ceci est lorsque vous faites une métaprogrammation de modèle, car les objets enum ne sont pas des valeurs, contrairement aux membres static const. Auparavant, il s'agissait d'une solution de contournement courante pour les compilateurs qui ne vous laissaient pas initialiser les constantes intégrales statiques dans la définition de la classe. Ceci est expliqué dans une autre question .

4

Quand vous utilisez
enum {color = 1}
vous n'utilisez pas de mémoire, c'est comme
#define color 1 

Si vous déclarez une variable
int color=1 Ensuite, vous utilisez de la mémoire pour une valeur à ne pas modifier.

1
atoMerz

Je ne le vois pas mentionné, une autre utilisation consiste à définir la portée de vos constantes. Je travaille actuellement sur du code qui a été écrit avec Visual Studio 2005 et il est maintenant porté sur Android - g ++. Dans VS2005, vous pourriez avoir un code comme celui-ci enum MyOpts { OPT1 = 1 }; et l'utiliser comme MyOpts :: OPT1 - et le compilateur ne s'en est pas plaint, même s'il n'est pas valide. g ++ signale que ce code est une erreur. Une solution consiste donc à utiliser une énumération anonyme comme suit: struct MyOpts { enum {OPT1 =1}; };, et les deux compilateurs sont désormais satisfaits.

0
marcinj

Réponse

Lisibilité et performance.
Les détails sont décrits en tant que notes aux exemples ci-dessous.

Cas d'utilisation

Exemple personnel

Dans nreal Engine 4 (moteur de jeu C++), j'ai la propriété suivante (variable de membre exposée au moteur):

/// Floor Slope.

UPROPERTY
(
    Category = "Movement",
    VisibleInstanceOnly,

    BlueprintGetter = "BP_GetFloorSlope",
    BlueprintReadOnly,

    meta =
    (
        ConsoleVariable = "Movement.FloorSlope",
        DisplayName     = "Floor Slope",
        ExposeOnSpawn   = true,
        NoAutoLoad
    )
)

float FloorSlope = -1.f;

C'est une valeur de la pente du sol sur laquelle le joueur se tient (valeur [0; 90) °), le cas échéant.
En raison des limitations du moteur, il ne peut être ni std::optional ni TOptional .
J'ai proposé une solution pour ajouter une autre variable auto-explicable bIsOnFloor.

bool  bIsOnFloor = false;

Mon configurateur interne uniquement C++ pour FloorSlope se présente comme suit:

void UMovement::SetFloorSlope(const float& FloorSlope) noexcept
    contract [[expects audit: FloorSlope >= 0._deg && FloorSlope < 90._deg]]
{
    this->bIsOnFloor = true;
    this->FloorSlope = FloorSlope;

    AUI::UI->Debug->FloorSlope = FString::Printf(L"Floor Slope: %2.0f", FloorSlope);
};

Ajouter le cas particulier où le paramètre FloorSlope prendrait l'argument de -1.f serait difficile à deviner et non convivial. Au lieu de cela, je créerais plutôt le champ Falseenum:

enum { False };

De cette façon, je peux simplement surcharger la fonction SetFloorSlope qui prend intuitive False au lieu de -1.f.

void UMovement::SetFloorSlope([[maybe_unused]] const decltype(False)&) noexcept
{
    this->bIsOnFloor = false;
    this->FloorSlope = -1.f;

    AUI::UI->Debug->FloorSlope = L"Floor Slope:  —";
};


Quand un personnage de joueur frappe un plancher en appliquant la gravité dessus, j'appelle simplement:

SetFloorSlope(FloorSlope);

… Où FloorSlope est une float valeur ∈ [0; 90) °. Sinon (si ça ne touche pas le sol), j'appelle:

SetFloorSlope(False);

Cette forme (par opposition à passer -1.f) est beaucoup plus lisible et explicite.

Exemple de moteur

Un autre exemple peut être d'empêcher ou de forcer l'initialisation. Mentionné ci-dessus, Unreal Engine 4 utilise couramment FHitResultstruct contenant des informations sur un coup d'une trace, comme le point d'impact et la normale à la surface à cet endroit.

Ce complexe struct appelle la méthode Init par défaut, en définissant des valeurs pour certaines variables membres. Cela peut être forcé ou empêché (documents publics: FHitResult #constructor ):

FHitResult()
{
    Init();
}

explicit FHitResult(float InTime)
{
    Init();
    Time = InTime;
}

explicit FHitResult(EForceInit InInit)
{
    Init();
}

explicit FHitResult(ENoInit NoInit)
{
}

Epic Games définit de la même façon enum, mais ajoute des noms redondants enum:

enum EForceInit 
{
    ForceInit,
    ForceInitToZero
};
enum ENoInit {NoInit};

Le fait de passer NoInit au constructeur de FHitResult empêche l'initialisation, ce qui peut entraîner des gains de performances en ne initialisant pas les valeurs qui seront initialisées ailleurs.

Exemple communautaire

FHitResult(NoInit) utilisation dans DamirH post sur Analyse complète de la série GameplayAbilities :

//A struct for temporary holding of actors (and transforms) of actors that we hit
//that don't have an ASC. Used for environment impact GameplayCues.
struct FNonAbilityTarget
{
    FGameplayTagContainer CueContainer;
    TWeakObjectPtr<AActor> TargetActor;
    FHitResult TargetHitResult;
    bool bHasHitResult;

public:
    FNonAbilityTarget()
        : CueContainer(FGameplayTagContainer())
        , TargetActor(nullptr)
        , TargetHitResult(FHitResult(ENoInit::NoInit))
        , bHasHitResult(false)
    {
    }

// (…)
0
user1180790