例如,这是我写它的方式,它编译并且工作得很好:
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
没有?
答案 0 :(得分:23)
显着的区别在于以下代码编译和链接:
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静态数据成员是整数类型或枚举类型,则其在类定义中的声明可以指定一个大括号或者相等初始化程序,其中每个initializer子句都是赋值表达式是一个常量表达式([expr.const])。 如果程序中使用了odr-used ([basic.def.odr]),则成员仍应在命名空间作用域中定义,并且命名空间作用域定义不应包含初始化程序。 [...]
换句话说,static const bool value = true;
是一个声明,而不是一个定义,你不能使用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以来的定义。因此,您不必再定义它了,您将能够直接使用它。
正如评论中提到的那样(感谢@Yakk确认了这一点)我也试图解释上面提到的命名常量如何绑定到const引用。
[expr.const/3]引入了整型常量表达式,并提到未编译的enum
,并将其隐式转换为 prvalue 。
[dcl.init.ref/5]和[class.temporary/2]完成剩下的工作,因为他们会依赖参考绑定和临时工具。
答案 1 :(得分:6)
是否只是因为
static const
变量使用了一个字节的内存,而enum
没有?
是的,这就是原因。
static const bool value = true;
会占用记忆,而
enum { value = true };
没有按&#39;吨
答案 2 :(得分:6)
是的,你是对的:enum { value = true };
并没有占用任何记忆。
此外,在C ++ 11之前,几乎只有 方式实现这一点:static const bool value = true;
仅在C ++ 11以后的类定义中是合法的。虽然可能首选constexpr
。
答案 3 :(得分:0)
它也是包含它的每个目标文件中的另一个符号,没有任何好处。如果你使用符号折叠(--gc-sections),你将会用完可分离的部分&amp;膨胀你的二进制文件。
答案 4 :(得分:0)
有些人写的不太明显,enum
而不是static bool const
,因为他们没有意识到应该进行其他更改。
如果需要其地址,例如,如果将C ++传递给此函数foo
,则需要定义该对象:
void foo(bool const &);
但是,通过定义对象解决问题实际上不是解决此问题的正确方法。以下是一些替代方法:
Small objects should not be passed by reference。更改应该是从函数签名中删除const &
,不为对象添加定义。
在无法更改函数签名的情况下,可以在调用中显式创建一个临时项:foo( bool { Cls::mbr } )
但是,这是编译时间信息!因此,foo
应该是具有T
和T*
重载的模板,或者应该是bool
的专用模板。
该第三种解决方案的优点是消除了不必要的运行时检查(希望通过编译器进行优化),并且还允许独立处理指针和非指针情况,从而可能使代码更清晰。