【问题标题】:The usage of anonymous enums匿名枚举的使用
【发布时间】:2011-11-01 02:22:29
【问题描述】:

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

enum { color = 1 };

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

【问题讨论】:

  • 你可以,但这样可以节省内存。
  • 用什么方法可以节省内存?
  • @ratzip:请参阅下面的答案。
  • @ratzip: 编译器可能会将其刻录到 CPU 指令中,例如 foo = color; --> mov 1, foo
  • 编译器只是将color 替换为1。就像数字 42 不会占用程序中的内存一样,枚举也不会。顺便说一句,你让我拼错 color :)

标签: c++ enums


【解决方案1】:
(1) int color = 1;

color 是可变的(意外地)。

(2) enum { color = 1 };

color 无法更改。

enum 的另一个选项是,

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

enumconst int 提供完全相同的概念;这是一个选择问题。关于enums 节省空间的普遍看法,IMO 没有与之相关的内存限制,编译器足够聪明,可以在需要时优化const int

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

【讨论】:

  • 但一般来说,const int 应该是首选,因为语义更符合意图(它不是枚举!)。
  • 我认为在const int color = 1; 中,如果你真的愿意,颜色仍然可以更改,const_cast;但是,无法更改枚举。
  • @Eran:不。如果它被声明为 const,你不能const_cast constness 离开:这是未定义的行为。
  • @Oli,好吧,比起const int,我更喜欢enum; IMO 他们更有条理,不太可能成为const_cast 的受害者并导致 UB。
  • @iammilind:“更有条理”? YMMV,但我的方法是确保我的代码语义尽可能符合我的意图。如果你不小心抛弃了 constness,那么你就有更大的问题了!
【解决方案2】:

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

如果您使用const int color = 1;,那么您将解决可变性问题,但如果有人使用color (const int* p = &color;) 的地址,则必须为其分配空间。这可能没什么大不了的,但除非您明确希望人们能够获取color 的地址,否则您不妨阻止它。

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


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

【讨论】:

  • const int 是不可变的,并且可能不占用任何空间,具体取决于编译器选择执行的操作。
  • @Oli Charlesworth 常量如何不占用空间?如果我在运行时初始化常量怎么办?
  • @AtoMerZ:这就是我说“这取决于”的原因。此外,这也不是一个公平的比较。您不能在运行时更改 enum
  • 我只是说常量不会做同样的事情。常量有不同的用途。
  • @AtoMerZ:如果你在运行时初始化常量,那么编译器显然无法优化它。
【解决方案3】:

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

【讨论】:

  • +1:这是正确答案。除此之外,没有正当理由使用enum 代替const int
  • +1 应该被接受。伟大、简短和甜蜜的解释。
  • 我也喜欢这个答案。通过翻阅 Linux 的内核源代码,我现在已经意识到了这个trick。使用 enum 而不是 const int 替代方案的一个可能原因是,使用 enum 允许对具有相同用途的名称进行分组的有组织的方法。
【解决方案4】:

当你使用
enum {color = 1}
你没有使用任何内存,就像
#define color 1

如果你声明一个变量
int color=1 然后,您将占用内存以获取不会更改的值。

【讨论】:

  • 除非编译器决定为其分配内存,否则您不会占用任何内存。
  • 通过“定义”常量是变量并占用空间,我不是在谈论特定于编译器的优化。
  • “占用空间”是无关紧要的,除非您谈论的是编译器输出。如果您担心占用空间,那么您担心的是优化。没有理智的编译器会为未写入的变量分配空间。
  • const int x=1; const int* y=&x; cout << y;。声明后没有任何内容写入 x 。你可能想看看这个结果。
  • 好的,您发现了另一种编译器无法避免分配内存的情况。但这并不影响我的主要观点。
【解决方案5】:

如果这是旧代码,那么 enum 可能已被用于“enum hack”。

您可以了解有关“枚举破解”的更多信息,例如,在此链接中:enum hack

【讨论】:

  • 赞成,但我建议您在回答中也描述枚举黑客,以防该链接在未来 20 年内失效。
【解决方案6】:

当您进行模板元编程时,它的一个用途是,因为枚举对象不是左值,而static const 成员是。对于不允许您在类定义中初始化静态整数常量的编译器来说,它也曾经是一种常见的解决方法。 another question 对此进行了解释。

【讨论】:

  • 我相信元编程的用途已经不复存在了,因为 C++11 添加了constexpr
【解决方案7】:

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

【讨论】:

    【解决方案8】:

    回答

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

    用例

    个人例子

    Unreal Engine 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::optional 也不是TOptional
    我想出了一个解决方案来添加另一个可自我解释的变量bIsOnFloor

    bool  bIsOnFloor = false;
    

    FloorSlope 的唯一 C++ 内部设置器采用以下形式:

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

    ... 其中FloorSlopefloat 值 ∈ [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 定义了类似的enums,但添加了多余的enum 名称:

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

    NoInit 传递给FHitResult 的构造函数会阻止初始化,通过不初始化将在其他地方初始化的值会导致性能提升。

    社区示例

    FHitResult(NoInit) 在 DamirH 的 post综合游戏能力分析系列中的用法

    //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)
        {
        }
    
    // (…)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-05-08
      • 1970-01-01
      • 1970-01-01
      • 2013-10-06
      • 1970-01-01
      • 2019-02-18
      相关资源
      最近更新 更多