c++ 匿名枚举的用法

46scxncf  于 2022-12-15  发布在  其他
关注(0)|答案(8)|浏览(332)

匿名enum声明的用途是什么,例如:

enum { color = 1 };

为什么不直接声明int color = 1呢?

smdncfj3

smdncfj31#

这就是declaring a compile-time integer constant的枚举技巧。它的优点是保证没有变量被示例化,因此没有运行时开销。无论如何,大多数编译器不会引入整型常量的开销。

cotxawn7

cotxawn72#

枚举不占用任何空间,并且是不可变的。
如果你使用const int color = 1;,那么你就可以解决可变性问题,但是如果有人使用了colorconst int* p = &color;)的地址,那么就必须为它分配空间。这可能不是什么大问题,但是除非你明确地“希望”人们能够使用color的地址,否则你最好阻止它。
此外,当在类中声明常量字段时,它必须是static const(对于现代C++不是这样),并且不是所有编译器都支持静态常量成员的内联初始化。

**免责声明:**此答案不应被视为对所有数值常量使用enum的建议。您应该做您(或您的同事)认为更具可读性的事情。答案只是列出了一些您 * 可能 * 更喜欢使用enum的原因。

hgc7kmma

hgc7kmma3#

如果这是旧代码,那么枚举可能已经被用于“枚举黑客”。
你可以了解更多关于“枚举黑客”,例如,在这个链接:enum hack
简而言之:这允许定义一次值,就像#define或定义变量一样,但与#define不同-这里编译器确保值是一个数字(int,r-value)并防止你做各种各样的恶作剧,你可以做与预编译器的简单“搜索和替换”,并不像定义一个变量-从来没有占用空间下的任何编译器或配置,并防止对它进行更改(如果您足够努力,即使是const变量有时也可以更改)。

1tuwyuhd

1tuwyuhd4#

(1) int color = 1;

color是可变的(意外地)。

(2) enum { color = 1 };

无法更改color
enum的另一个选项是,

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

enumconst int提供了完全相同的概念;关于enum保存空间的普遍看法,我认为没有与此相关的内存限制,编译器足够聪明,可以在需要时优化const int
[Note:如果有人试图在const int上使用const_cast<>;它将导致未定义的行为(这是不好的)。然而,同样的情况对enum是不可能的。所以我个人最喜欢的是enum]

bnlyeluc

bnlyeluc5#

这种方法的一个用途是当你在做模板元编程的时候,因为枚举对象不是左值,而static const成员是。它也曾经是不允许你在类定义中初始化静态整型常量的编译器的一个常见的解决方案。这在another question中解释。

kyvafyod

kyvafyod6#

当您使用
enum {color = 1}
你不用任何记忆就像
#define color 1
如果你声明一个变量
那么你就占用了内存来存放一个不需要修改的值。

nuypyhwy

nuypyhwy7#

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 Falseenum 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 FHitResultstruct 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 enum s 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)
    {
    }

// (…)
tjrkku2a

tjrkku2a8#

我没有看到它被提及,另一个用途是限定常量的范围。我目前正在使用Visual Studio 2005编写代码,它现在被移植到android - g++。在VS 2005中,你可以有这样的代码enum MyOpts { OPT1 = 1 };,并将其用作MyOpts::OPT 1-编译器没有抱怨它,即使它是无效的。g++将这样的代码报告为错误,所以一个解决方案是使用匿名枚举,如下所示:struct MyOpts { enum {OPT1 =1}; };,现在两个编译器都很满意。

相关问题