it-swarm.com.de

Die Verwendung anonymer Enums

Was ist der Zweck von anonymen enum-Deklarationen wie:

enum { color = 1 };

Warum nicht einfach int color = 1 deklarieren?

59
user707549

Aufzählungen belegen keinen Platz und sind unveränderlich.

Wenn Sie const int color = 1; verwenden, lösen Sie das Problem der Veränderlichkeit. Wenn jedoch jemand die Adresse von color (const int* p = &color;) verwendet, muss ihm Speicherplatz zugewiesen werden. Dies mag keine große Sache sein, aber wenn Sie nicht ausdrücklich möchten, dass Personen die Adresse von color annehmen können, können Sie dies genauso gut verhindern .

Auch wenn ein konstantes Feld in einer Klasse deklariert wird, muss es static const sein. (gilt nicht für modernes C++) und nicht alle Compiler unterstützen die Inline-Initialisierung statischer const-Member.


Haftungsausschluss: Diese Antwort sollte nicht als Hinweis auf die Verwendung von enum für alle numerischen Konstanten verstanden werden. Sie sollten das tun, was Sie (oder Ihre Kuhkenner) für besser lesbar halten. In der Antwort werden nur einige Gründe aufgeführt, aus denen möglicherweise die Verwendung eines enum vorziehen.

55
Motti

Das ist ein sogenannter Enumentrick für Deklarieren einer Ganzzahlkonstante zur Kompilierung . Der Vorteil ist, dass garantiert wird, dass keine Variable instanziiert wird und daher kein Laufzeitaufwand entsteht. Die meisten Compiler führen ohnehin keinen Overhead mit Integer-Konstanten ein.

80
sharptooth

Wenn dies alter Code ist, wurde für den "Enum-Hack" möglicherweise Enum verwendet.

Mehr über den "Enum Hack" erfahren Sie beispielsweise unter diesem Link: Enum Hack

6
Eran Zimmerman
(1) int color = 1;

color ist veränderlich (versehentlich).

(2) enum { color = 1 };

color kann nicht geändert werden.

Die andere Option für enum ist

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

Sowohl enum als auch const int bieten genau dasselbe Konzept. Es ist eine Frage der Wahl. Im Hinblick auf die verbreitete Meinung, dass enums Speicherplatz spart, gibt es keine Speicherbeschränkung im Zusammenhang mit IMO. Compiler sind intelligent genug, um const int bei Bedarf zu optimieren.

[Hinweis: Wenn jemand versucht, const_cast<> für einen const int zu verwenden; Dies führt zu undefiniertem Verhalten (was schlecht ist). Gleiches ist jedoch für enum nicht möglich. Mein persönlicher Favorit ist also enum]

4
iammilind

Dies kann zum Beispiel bei der Metaprogrammierung von Vorlagen verwendet werden, da Enumerationsobjekte keine l-Werte sind und static const-Member. Es war auch eine häufige Problemumgehung für Compiler, bei der Sie keine statischen Integralkonstanten in der Klassendefinition initialisieren konnten. Dies wird in eine andere Frage erklärt.

Wenn Sie verwenden
enum {color = 1}
Sie verwenden keinen Speicher, es ist wie
#define color 1 

Wenn Sie eine Variable deklarieren
int color=1 Dann nehmen Sie den Speicher für einen Wert, der nicht geändert werden soll.

1
atoMerz

Ich sehe es nicht erwähnt, eine andere Verwendung ist der Umfang Ihrer Konstanten. Ich arbeite derzeit an Code, der mit Visual Studio 2005 geschrieben wurde, und ist jetzt auf Android - g ++ portiert. In VS2005 können Sie Code wie diesen enum MyOpts { OPT1 = 1 }; verwenden und als MyOpts :: OPT1 verwenden - und der Compiler hat sich nicht beschwert, obwohl er nicht gültig ist. g ++ meldet einen solchen Code als Fehler. Eine Lösung besteht darin, anonyme Enummen wie folgt zu verwenden: struct MyOpts { enum {OPT1 =1}; };, und nun sind beide Compiler zufrieden.

0
marcinj

Antworten

Lesbarkeit und Leistung.
Details werden nachfolgend als Anmerkungen zu Beispielen beschrieben.

Anwendungsfälle

Persönliches Beispiel

In nreal Engine 4 (C++ - Spiel-Engine) habe ich folgende Eigenschaft (Engine-Exposed-Member-Variable):

/// Floor Slope.

UPROPERTY
(
    Category = "Movement",
    VisibleInstanceOnly,

    BlueprintGetter = "BP_GetFloorSlope",
    BlueprintReadOnly,

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

float FloorSlope = -1.f;

Dies ist ein Wert des Bodenneigungsspielers, auf dem er steht (Wert ∈ [0; 90) °), falls vorhanden.
Aufgrund von Motorbeschränkungen kann es weder std::optional noch TOptional sein.
Ich habe eine Lösung gefunden, um eine weitere selbsterklärende Variable bIsOnFloor hinzuzufügen.

bool  bIsOnFloor = false;

Mein C++ einziger interner Setter für FloorSlope hat die folgende Form:

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);
};

Das Hinzufügen eines Sonderfalls, in dem der Parameter FloorSlope das Argument -1.f annehmen würde, ist schwer zu erraten und nicht benutzerfreundlich. Stattdessen würde ich lieber ein Falseenum Feld erstellen:

enum { False };

Auf diese Weise kann ich einfach die Funktion SetFloorSlope überladen, die anstelle von -1.f eine intuitive Funktion False verwendet.

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

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


Wenn ein Spielercharakter ein Stockwerk berührt, indem er die Schwerkraft auf ein Häkchen ausübt, rufe ich einfach:

SetFloorSlope(FloorSlope);

… Wobei FloorSlope ein float-Wert ist ∈ [0; 90) °. Ansonsten (wenn es keinen Boden berührt) rufe ich an:

SetFloorSlope(False);

Diese Form (im Gegensatz zum Bestehen von -1.f) ist viel lesbarer und selbsterklärender.

Motor Beispiel

Ein anderes Beispiel kann sein, die Initialisierung zu verhindern oder zu erzwingen. Die oben erwähnte Unreal Engine 4 verwendet normalerweise FHitResultstruct, das Informationen über einen Treffer einer Spur enthält, z. B. Aufprallpunkt und Oberflächennormale an diesem Punkt.

Diese komplexe struct-Methode ruft standardmäßig die Init-Methode auf und legt einige Werte für bestimmte Mitgliedsvariablen fest. Dies kann erzwungen oder verhindert werden (öffentliche Dokumente: FHitResult #constructor ):

FHitResult()
{
    Init();
}

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

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

explicit FHitResult(ENoInit NoInit)
{
}

Epic Games definiert solche enum-ähnlichen Namen, fügt jedoch redundante enum-Namen hinzu:

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

Die Übergabe von NoInit an den Konstruktor von FHitResult verhindert die Initialisierung. Dies kann zu einer Leistungssteigerung führen, wenn Werte, die an anderer Stelle initialisiert werden, nicht initialisiert werden.

Community-Beispiel

FHitResult(NoInit) Verwendung in DamirHs post on Umfassende GameplayAbilities-Analyseserie :

//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