【问题标题】:static const Member Value vs. Member enum : Which Method is Better & Why?static const Member Value vs. Member enum:哪种方法更好?为什么?
【发布时间】:2008-10-15 14:42:33
【问题描述】:

如果您想将某个常量值与一个类相关联,这里有两种方法可以实现相同的目标:

class Foo
{
public:
    static const size_t Life = 42;
};

class Bar
{
public:
    enum {Life = 42};
};

从客户的角度来看,它们在句法和语义上似乎是相同的:

size_t fooLife = Foo::Life;
size_t barLife = Bar::Life;

除了纯粹的风格问题之外,还有什么理由比另一种更受欢迎吗?

【问题讨论】:

  • 现在,与 static const 相比,C++11 的 static constexpr 更好,因为它强制编译器必须能够在编译时解析值,这表明意图并可能避免意外。这也可能有助于以后的优化,但我不保证这种猜测!
  • @alfC ...和?我没有说不是,因为我不是在谈论enums。

标签: c++ class-design


【解决方案1】:

enum hack 曾经是必要的,因为许多编译器不支持值的就地初始化。由于这不再是问题,请选择其他选项。现代编译器也能够优化这个常量,使其不需要存储空间。

不使用static const 变体的唯一原因是如果你想禁止获取值的地址:你不能获取enum 值的地址,而你可以获取一个常量的地址(这将提示编译器为该值保留空间,但只有如果它的地址真的被占用了)。

此外,除非常量也明确定义,否则获取地址将产生链接时错误。注意它仍然可以在声明的地方初始化:

struct foo {
    static int const bar = 42; // Declaration, initialization.
};

int const foo::bar; // Definition.

【讨论】:

  • 实际上 - 你的编译器不应该为常量分配任何存储空间。该标准明确指出,仅当对象被“使用”时,即。它需要的地址,是所需对象的定义。然后开发人员必须明确提供定义!
  • 如果你取了一个“静态常量”的地址但没有定义它,那么它应该会导致一个链接器错误。
  • Richard,你在这里混合了初始化和定义;您可以很好地初始化常量内联并单独定义它。但你是对的,我需要澄清我的帖子。
  • 将您的示例与 VS2010 一起使用,链接器抱怨 foo::bar 具有重复的定义(即使我添加了明确采用 foo:bar 地址的代码)。那是编译器错误吗? foo 是静态库的一部分有什么不同吗?
  • @Adrian 简单,您可能在标头中定义常量并在多个编译单元中使用它。由于单一定义规则的常见原因,您不能这样做。你需要在实现文件中定义常量而不是头文件。
【解决方案2】:

它们并不相同:

size_t *pLife1 = &Foo::Life;
size_t *pLife2 = &Bar::Life;

【讨论】:

  • 基本上就是我所说的,用更少的话来说。 ;-)
  • 代码示例应该是(添加了consts):const size_t *pLife1 = &Foo::Life;const size_t *pLife2 = &Bar::Life; 第一个没问题,但第二个会产生编译器错误,因为枚举器列表中的标识符被声明为常量,不能取常量的地址。
【解决方案3】:

一个区别是枚举定义了一种可以用作方法参数的类型,例如,为了获得更好的类型检查。两者都被编译器视为编译时常量,因此它们应该生成相同的代码。

【讨论】:

    【解决方案4】:

    static const 值被视为 r 值,就像您将看到的 99% 代码中的 enum 一样。常量 r 值永远不会为它们生成内存。 enum 常量的优点是它们不能成为另外 1% 的左值。 static const 值是类型安全的,并允许浮点数、c 字符串等。

    如果Foo::Life 有与之关联的内存,编译器会将其设为左值。通常的方法是获取它的地址。例如&Foo::Life;

    这是一个 GCC 将使用地址的微妙示例:

    int foo = rand()? Foo::Life: Foo::Everthing;
    

    编译器生成的代码使用LifeEverything 的地址。更糟糕的是,这只会产生关于Foo::LifeFoo::Everything 缺失地址的链接器错误。这种行为完全符合标准,尽管显然是不可取的。还有其他编译器特定的方式可以发生这种情况,并且符合所有标准。

    一旦你有一个符合标准的 c++11 编译器,正确的代码就是

    class Foo {
     public:
      constexpr size_t Life = 42;
    };
    

    保证始终是左值,并且是类型安全的,两全其美。

    【讨论】:

    • 您的意思是“保证始终是 rvalue”吗?这比你实际写的要大得多,但仍然不能保证。 constexpr 如果地址被占用,则可以分配存储空间,在这种情况下,它在初始化后就等同于 const 变量。但是可以肯定的是,如果您不这样做,那么 constexpr 是无存储空间的,并且出于各种原因是最佳解决方案。
    • constexpr size_t Life = 42; 会导致编译错误。它也必须是静态的。这是根据 Clang 3.8。
    • “这是一个 GCC 将使用地址的微妙示例”doesn't apply with GCC 5.1。即使引用非constexprstatic const 也不会实际上获取地址,除非您尝试获取引用的地址。不知道其他编译器如何处理这个问题,但你特别提到了 GCC。
    【解决方案5】:

    好吧,如果需要,您可以获取静态 const 成员值的地址。你必须声明一个单独的枚举类型的成员变量来获取它的地址。

    【讨论】:

      【解决方案6】:

      还有第三种解决方案?

      一个细微的区别是枚举必须在标题中定义,并且对所有人可见。当您避免依赖时,这很痛苦。例如,在 PImpl 中,添加枚举会适得其反:

      // MyPImpl.hpp
      
      class MyImpl ;
      
      class MyPimpl
      {
         public :
            enum { Life = 42 } ;
         private :
            MyImpl * myImpl ;
      }
      

      另一个第三个解决方案将是问题中提出的“const static”替代方案的变体:在标头中声明变量,但在源中定义它:

      // MyPImpl.hpp
      
      class MyImpl ;
      
      class MyPimpl
      {
         public :
            static const int Life ;
         private :
            MyImpl * myImpl ;
      }
      

      .

      // MyPImpl.cpp
      const int MyPImpl::Life = 42 ;
      

      请注意,MyPImpl::Life 的值对 MyPImpl 的用户(包括 MyPImpl.hpp)是隐藏的。

      这将使 MyPimpl 作者能够根据需要更改“Life”的值,而无需 MyPImpl 用户重新编译,这是 PImpl 的总体目标。

      【讨论】:

        猜你喜欢
        • 2020-04-19
        • 1970-01-01
        • 2020-09-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-01-28
        • 2014-10-22
        相关资源
        最近更新 更多