在类型特征中,为什么人们使用枚举而不是静态常量来表示值?

22
例如,这是我如何编写它的方式,它可以编译并正常工作:
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呢?是因为static const变量使用了一个字节的内存,而enum没有吗?

5个回答

25
一个显著的区别在于以下代码可以编译和链接:
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);
}

以下代码无法正常工作(您会得到一个“undefined reference”错误,指向“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-usevalue
另一方面,根据[dcl.enum/1](我强调):

枚举是有命名常量的不同类型。

这些命名常量可以像上面的示例一样作为const引用。


作为一个附注,如果你在C++11/14中使用静态constexpr数据成员,类似的情况也适用:
template<typename T>
struct is_pointer<T*> { static constexpr bool value = true; }; 

这不太好用,我发现它们之间存在微妙的差异。我在这里得到了帮助,从答案中获得了一些不错的提示。引用标准可以更好地解释底层发生的事情。
请注意,像上面那样声明staticconstexpr数据成员也是自C++17以来的定义。因此,您将不再需要定义它,并且将能够直接使用odr。
如评论中所提到的(感谢@Yakk的确认),我也试图解释上述“命名常量”是如何绑定到const引用的。[expr.const/3]引入了“整数常量表达式”,并通过说它被隐式转换为prvalue来提到未作用域的enum[dcl.init.ref/5][class.temporary/2]则完成了其余部分,因为它们规定了引用绑定和临时变量。

2
点赞。楼主应该接受这个答案代替我的。 - Bathsheba
1
C++17的内联变量可以解决这个问题,我相信。 - Yakk - Adam Nevraumont
@Bathsheba 这跟我的一个问题非常相似,当我问这个问题时,我做了一些研究。类似的答案适用于我的问题。 - skypjack
2
@Thomas:static 数据成员只有在定义时才存在于内存中,如果它们的地址被取出(通过指针或引用),则必须定义。当它节省内存时,这很棒,但当链接器抱怨你忘记了定义时,这很烦人 叹气 - Matthieu M.
1
@skypjack 看起来很合理。 - Yakk - Adam Nevraumont
显示剩余4条评论

6

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

此外,在 C++11 之前,这几乎是唯一实现这个目标的方法:static const bool value = true; 只有在 C++11 以后的类定义中才合法。虽然可能更倾向于使用 constexpr


3
很遗憾,这不是一个更好的原因之一。;-) - skypjack
1
你有“只在C++11及以后版本中合法”的参考资料吗?我认为“static const”成员自C++‘98以来就一直存在于该语言中。 - Richard Corden
@RichardCorden 确实如此。也许 Bathsheba 在想 NSDMI,但这与此无关。 - Lightness Races in Orbit

6

这是因为static const变量占用了一个字节的内存,而enum没有。

是的,这就是原因。

static const bool value = true;

会占用内存,而

enum { value = true };

不支持。


2
不幸的是,这不是一个更好的原因。;-) - skypjack
3
在类定义中,static const bool value = true; 不会占用任何内存。只有在 valueodr-used(即被用于表达式求值或取其地址)时,相应的类外定义才会占用内存。 - M.M
根据@M.M的评论,这更加复杂,尤其是自C++ '11以来。 - Richard Corden

1
有些人会写不太明显的enum而不是static bool const,因为他们没有意识到还有其他需要修改的地方。
如果需要它的地址,例如将其传递给foo函数,C++要求定义该对象。
void foo(bool const &);

然而,通过定义对象来解决问题实际上并不是这个问题的正确修复方法。以下是一些替代方案:
  1. 不应该将小对象按引用传递。更改应该是从函数签名中删除const &,而不是添加对象的定义。

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

  3. 但是,这是编译时信息!因此,foo 应该是一个带有 TT* 重载的模板,或者用 bool 进行专门化。

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


1
有趣,但我不确定这些中的任何一个是如何回答问题的……问题在哪里是“const&”,“函数签名”,“foo”? - Thomas
@Thomas感谢你提醒我注意这个问题。我想说的是,最好让答案本身就包含完整信息,而不是参考其他回答。 - Richard Corden
啊,那种不知不觉中发生的思维飞跃,我很了解。现在我明白你的意思了,好东西! - Thomas

0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接