The usage of anonymous enums

后端 未结 8 568
无人及你
无人及你 2020-11-29 19:40

What is the purpose of anonymous enum declarations such as:

enum { color = 1 };

Why not just declare int color = 1

相关标签:
8条回答
  • 2020-11-29 20:39

    Answer

    Readability and performance.
    Details are describbed as notes to examples below.

    Use cases

    Personal example

    In Unreal Engine 4 (C++ game engine), I have following property (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;
    

    This is a value of floor slope player is standing on (value ∈ [0; 90)°), if any.
    Because of engine limitations, it cannot be neither std::optional nor TOptional.
    I've came up with a solution to add another self explainable variable bIsOnFloor.

    bool  bIsOnFloor = false;
    

    My C++ only internal setter for FloorSlope takes the following 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);
    };
    

    Adding special case where FloorSlope parameter would take argument of -1.f would be hard to guess and not user friendly. Instead, I'd rather create False enum field:

    enum { False };
    

    This way, I can simply overload SetFloorSlope function that takes intuitive False instead of -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:  —";
    };
    


    When a player character hits a floor upon applying gravity to it on tick, I simply call:

    SetFloorSlope(FloorSlope);
    

    … where FloorSlope is a float value ∈ [0; 90)°. Otherwise (if it does not hits a floor), I call:

    SetFloorSlope(False);
    

    This form (as opposed to passing -1.f) is much more readable, and self explanatory.

    Engine example

    Another example may be to prevent or force initialization. Mentioned above Unreal Engine 4 commonly uses FHitResult struct containing information about one hit of a trace, such as point of impact and surface normal at that point.

    This complex struct calls Init method by default, setting some values to certain member variables. This can be forced or prevented (public docs: FHitResult #constructor):

    FHitResult()
    {
        Init();
    }
    
    explicit FHitResult(float InTime)
    {
        Init();
        Time = InTime;
    }
    
    explicit FHitResult(EForceInit InInit)
    {
        Init();
    }
    
    explicit FHitResult(ENoInit NoInit)
    {
    }
    

    Epic Games defines such enums similiar, but adds redundant enum names:

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

    Passing NoInit to the constructor of FHitResult prevents initialization, what can lead to performance gain by not initializing values that will be initialized elsewhere.

    Community example

    FHitResult(NoInit) usage in DamirH's post on Comprehensive GameplayAbilities Analysis Series:

    //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 讨论(0)
  • 2020-11-29 20:40

    I dont see it mentioned, another use is to scope your constants. I currently work on code that was written using Visual Studio 2005, and it is now ported to android - g++. In VS2005 you could have code like this enum MyOpts { OPT1 = 1 }; and use it as MyOpts::OPT1 - and compiler did not complain about it, even though it is not valid. g++ reports such code as error, so one solution is to use anonymous enum as follows: struct MyOpts { enum {OPT1 =1}; };, and now both compilers are happy.

    0 讨论(0)
提交回复
热议问题