【问题标题】:In a type trait, why do people use enum rather than static const for the value?在类型特征中,为什么人们使用 enum 而不是 static const 作为值?
【发布时间】:2017-02-22 10:32:18
【问题描述】:

例如,我就是这样写的,它编译和工作得很好:

template<typename T> struct is_pointer<T*> {
  static const bool value = true;
}

那为什么有些人写的不那么明显

template<typename T> struct is_pointer<T*> {
  enum { value = true };
}      

相反?仅仅是因为static const 变量使用了一个字节的内存,而enum 没有吗?

【问题讨论】:

    标签: c++ templates enums static constants


    【解决方案1】:

    一个显着的区别在于以下代码的编译和链接:

    template<typename>
    struct is_pointer { };
    
    template<typename T>  
    struct is_pointer<T*> {
      enum { value = true };
    };     
    
    void f(const bool &b) { }
    
    int main() {
      f(is_pointer<void*>::value);
    }
    

    以下内容不起作用(您会得到一个对value未定义引用):

    template<typename>
    struct is_pointer { };
    
    template<typename T>
    struct is_pointer<T*> {
      static const bool value = true;
    };
    
    void f(const bool &b) { }
    
    int main() {
      f(is_pointer<void*>::value);
    }
    

    当然,除非您在某处添加以下行,否则它不起作用:

    template<typename T>
    const bool is_pointer<T*>::value;
    

    那是因为[class.static.data]/3(强调我的):

    如果一个非易失性非内联 const 静态数据成员是整数或枚举类型,它在类定义中的声明可以指定一个大括号或等式初始化器,其中每个初始化器子句都是一个赋值表达式是一个常量表达式 ([expr.const])。 如果该成员在程序中被 odr-used ([basic.def.odr]) 使用,则该成员仍应在命名空间范围内定义,并且命名空间范围定义不应包含初始化程序。 [...]

    换句话说,static const bool value = true; 是一个声明,而不是一个定义,您不能在 odr 中使用 value
    另一方面,根据[dcl.enum/1](强调我的):

    枚举是一种独特的类型具有命名常量

    如上例所示,这些命名常量可以被 const 引用。


    附带说明,如果您在 C++11/14 中使用 static constexpr 数据成员,则类似情况:

    template<typename T>
    struct is_pointer<T*> { static constexpr bool value = true; }; 
    

    这不太好,这就是我发现它们之间细微差别的方式。

    我在这里找到了帮助,以便从我得到的答案中得到一些很好的提示。
    对标准的引用有助于更好地解释幕后情况。

    请注意,像上面这样的 static constexpr 数据成员声明也是自 C++17 以来的定义。因此,您不必再定义它,而是可以直接使用它。


    正如 cmets 中提到的(感谢@Yakk 证实了这一点),我还试图解释上面提到的 命名常量 绑定到 const 引用是如何发生的。

    [expr.const/3] 引入了整型常量表达式,并提到了无范围的enums,表示它被隐式转换为prvalue
    [dcl.init.ref/5] 和@ 987654325@ 完成其余的工作,因为他们对引用绑定和临时对象进行了规定。

    【讨论】:

    • 赞成。 OP 应该代替我接受这个答案。
    • C++17 内联变量解决了这个问题,我相信。
    • @Bathsheba 这与我的一个问题非常相似,当我问这个问题时我做了我的研究。类似的答案适用于我的问题。
    • @Thomas: static 数据成员确实只有在定义时才存在于内存中,如果它们的地址被获取(通过指针或引用),这是必需的。当它节省内存时很棒,当链接器抱怨你忘记定义时很烦人 sigh.
    • @skypjack 似乎很合理。
    【解决方案2】:

    仅仅是因为static const 变量使用了一个字节的内存,而enum 没有吗?

    是的,就是这个原因。

    static const bool value = true;
    

    会占用内存,而

    enum { value = true };
    

    没有。

    【讨论】:

    • 不幸的是(?)这不是一个比另一个更好的唯一原因。 ;-)
    • static const bool value = true;,在类定义中,不占用任何内存。内存将被相应的类外定义占用,但仅当 valueodr-used 时才需要。
    • 根据@M.M 的评论,自 C++ '11 以来,它变得更加复杂,甚至更加复杂
    【解决方案3】:

    是的,你是对的:enum { value = true }; 不占用任何内存。

    此外,在 C++11 之前,它几乎是实现此目的的唯一方式:static const bool value = true; 仅在 C++11 以后的类定义中是合法的。虽然constexpr 可能是首选。

    【讨论】:

    • 不幸的是(?)这不是一个比另一个更好的唯一原因。 ;-)
    • 你有“仅从 C++ 11 开始合法”的参考吗?我相信“静态常量”成员自 C++ '98 以来就一直在使用该语言。
    • @RichardCorden 确实如此。也许 Bathsheba 正在考虑 NSDMI,但这与这里无关
    【解决方案4】:

    它也是每个包含它的目标文件中的另一个符号,没有任何好处。如果您使用符号折叠 (--gc-sections),您将用完可分离的部分并导致二进制文件膨胀。

    【讨论】:

      【解决方案5】:

      有些人写了不太明显的enum 而不是static bool const,因为他们没有意识到他们应该进行其他更改。

      如果需要地址,C++ 需要定义对象,例如如果它被传递给这个函数foo:

      void foo(bool const &);
      

      但是,通过定义对象来解决问题实际上并不是解决此问题的正确方法。以下是一些替代方案:

      1. Small objects should not be passed by reference。更改应该是从函数签名中删除const &amp;为对象添加定义。

      2. 在无法更改函数签名的情况下,可以在调用中显式创建临时:foo( bool { Cls::mbr } )

      3. 但是,这是编译时信息!因此,foo 应该是具有 TT* 重载的模板,或者专用于 bool

      这第三种解决方案的好处是消除了不必要的运行时检查(希望由编译器优化),还允许独立处理指针和非指针大小写,可能使代码更清晰。

      【讨论】:

      • 有趣,但我不确定这如何回答这个问题......问题中的“const &amp;”、“函数签名”、“foo”在哪里?
      • @Thomas 感谢您强调这一点。正如我确定您猜到的那样,我指的是其他答案的内容。更好的做法是让答案自成一体。
      • 啊,这种想法的飞跃,只发生在一个人的脑海中而没有意识到。我知道这一切。现在我明白你在说什么了,好东西!
      猜你喜欢
      • 1970-01-01
      • 2016-02-05
      • 2010-10-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多