匿名枚举的用法


71

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

enum { color = 1 };

为什么不只是声明int color = 1


1
您可以,但是可以节省内存。
2011年

1
用哪种方式可以节省精力?

1
@ratzip:请参阅下面的答案。
sharptooth 2011年

2
@ratzip:编译器可能会将其刻录到CPU指令中,例如foo = color;->mov 1, foo
Sebastian Mach

5
编译器仅替换color1。就像数字42不会占用程序中的内存一样,枚举也不会。顺便说一句,该死的你让我弄错了颜色:)
Motti

Answers:


64

枚举不占用任何空间并且是不可变的。

如果您使用过,const int color = 1;则可以解决可变性问题,但是如果有人使用colorconst int* p = &color;)的地址,则必须为其分配空间。这可能没什么大不了的,但是除非您明确希望人们能够使用color您的住址,否则最好不要这样做。

同样,当在类中声明一个常量字段时,它必须是常量static const (对于现代C ++而言并非如此),并且并非所有编译器都支持静态const成员的内联初始化。


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


4
const int是不可变的,并且可能不会占用任何空间,具体取决于编译器选择执行的操作。
奥利弗·查尔斯沃思

@Oli Charlesworth常量如何不占用空间?如果我在运行时初始化常量怎么办?
2011年

6
@AtoMerZ:这就是为什么我说“取决于”。而且,这并不是一个公平的比较。您无法enum在运行时更改!
奥利弗·查尔斯沃思

1
我只是说常量不会做同样的事情。常量有不同的用途。
2011年

@AtoMerZ:如果您在运行时初始化常量,那么编译器显然无法对其进行优化。
Matthieu M.

88

这就是所谓的枚举技巧,用于声明编译时整数常量。这样做的好处是可以确保没有实例化任何变量,因此没有运行时开销。无论如何,大多数编译器都不会引入整数常量的开销。


+1:这是正确的答案。没有正当理由使用enum代替const int此符号的正当理由。
奥利弗·查尔斯沃思

3
+1应该被接受。很棒,简短而甜美的解释。

我也喜欢这个答案。在Linux的内核源中进行反复思考现在我意识到了这个技巧。一个可能的原因使用枚举const int的另一种方法是,使用枚举允许分组样旨意名称的有组织的方法。
安德鲁·法兰加


5

一种用途是在进行模板元编程时,因为枚举对象不是左值,而static const成员是左值。对于过去不允许您在类定义中初始化静态整数常量的编译器,它也是一种常见的解决方法。这在另一个问题中得到解释。


1
我相信自从C ++ 11添加以来,元编程中的用途已经消失了constexpr
Shoe 2014年

4
(1) int color = 1;

color 是可变的(偶然)。

(2) enum { color = 1 };

color 无法更改。

另一个选择enum

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

双方enumconst int提供完全相同的概念; 这是一个选择问题。关于enum节省空间的流行观点,IMO没有与此相关的内存限制,编译器足够聪明,可以const int在需要时进行优化。

[注意:如果有人试图使用const_cast<>const int; 它将导致不确定的行为(这是不好的)。但是,对于则不可能enum。所以,我个人最喜欢的是enum]


6
但是一般来说,const int应该首选,因为语义与意图更紧密匹配(这不是枚举!)。
奥利弗·查尔斯沃思

我认为,const int color = 1;如果您确实愿意,可以使用更改颜色const_cast;枚举,但是,不能更改。
伊兰·齐默尔曼·戈嫩

5
@Eran:否。如果声明为const,则无法const_cast保持constness :这是未定义的行为。
R. Martinho Fernandes

1
@Oli,好了,我会更喜欢enumconst int; 国际海事组织,他们更有组织,不太可能受到const_castUB的伤害和结果。
iammilind

2
@iammilind:“更有条理”吗?YMMV,但是我的方法是确保我的代码的语义尽可能地符合我的意图。如果您不小心丢掉了constness,那么您将遇到更大的问题!
奥利弗·查尔斯沃思

1

当您使用时,
enum {color = 1}
您不会使用任何内存,就像
#define color 1

如果声明一个变量,
int color=1 那么您将占用不可变的值的内存。


2
除非编译器决定为其分配内存,否则您不会占用任何内存。
奥利弗·查尔斯沃思

通过“定义”常量是变量并且占用空间,我不是在谈论编译器特定的优化。
2011年

2
除非您在谈论编译器输出,否则“占用空间”是无关紧要的。而且,如果您担心占用空间,那么您会担心优化。没有理智的编译器会为未写入的变量分配空间。
奥利弗·查尔斯沃思

const int x=1; const int* y=&x; cout << y;。声明后,没有任何内容写入x。您可能想查看此结果。
2011年

2
好的,您发现了另一种情况,编译器无法避免分配内存。但这并不影响我的主要观点。
奥利弗·查尔斯沃思

1

回答

可读性和性能。
详细信息描述为以下示例的注释。

用例

个人榜样

虚幻引擎4(C ++游戏引擎)中,我具有以下属性(引擎暴露的成员变量):

/// Floor Slope.

UPROPERTY
(
    Category = "Movement",
    VisibleInstanceOnly,

    BlueprintGetter = "BP_GetFloorSlope",
    BlueprintReadOnly,

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

float FloorSlope = -1.f;

这是玩家所站的地面坡度值(值∈[0; 90)°)(如果有)。
由于引擎的限制,它既不能也不std::optionalTOptional
我想出了一个解决方案,以添加另一个可自我解释的变量bIsOnFloor

bool  bIsOnFloor = false;

我的仅限C ++内部设置器FloorSlope采用以下形式:

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

FloorSlope参数将作为参数的情况下添加特殊情况-1.f将很难猜测,而且也不友好。相反,我宁愿创建False enum字段:

enum { False };

这样,我可以简单地重载SetFloorSlope需要直观的功能False而不是-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:  —";
};


当玩家角色在对勾施加重力时撞到地板时,我简单地称呼:

SetFloorSlope(FloorSlope);

...其中FloorSlope是一个float值∈[0; 90)°。否则(如果它没有跌落),我打电话给:

SetFloorSlope(False);

这种形式(与通过 -1.f)更具可读性,并且易于解释。

引擎范例

另一个示例可能是阻止或强制初始化。以上提到的虚幻引擎4常用FHitResult struct包含有关轨迹的一次命中的信息,例如撞击点和该点的表面法线。

默认情况下,此复杂struct调用Init方法为某些成员变量设置一些值。这可以被强制或阻止(公共文档:FHitResult#constructor):

FHitResult()
{
    Init();
}

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

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

explicit FHitResult(ENoInit NoInit)
{
}

Epic Games定义了enum类似的enum名称,但添加了多余的名称:

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

传递NoInitFHitResultprevent初始化的构造函数,这可以通过不初始化将在其他地方初始化的值来提高性能。

社区例子

FHitResult(NoInit)DamirH在“综合游戏能力分析系列”中帖子中的用法:

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.