对静态constexpr char 的未定义引用
我想在我的类中有一个static const
char
数组。 海湾合作委员会抱怨说,我应该使用constexpr
,但现在它告诉我这是一个未定义的参考。 如果我让数组成为非成员,那么它编译。 到底是怎么回事?
// .hpp struct foo { void bar(); static constexpr char baz[] = "quz"; }; // .cpp void foo::bar() { std::string str(baz); // undefined reference to baz }
添加到您的cpp文件中:
constexpr char foo::baz[];
原因:您必须提供静态成员的定义以及声明。 声明和初始化器进入类定义,但成员定义必须分开。
在C ++ 03中,我们只允许在types初始化程序中为const整型或常量枚举types提供 ,在C ++ 11中使用了constexpr,这被扩展为字面types 。
在C ++ 11中,我们不需要为一个静态的constexpr成员提供一个名称空间的作用域定义,如果它没有被使用的话 ,我们可以从C ++ 11标准的草案9.4.2
[class.static.data ]这说( 重点是我的前进 ):
[…]文字types的静态数据成员可以在类定义中用constexpr说明符声明; 如果是的话,它的声明应该指定一个括号或者等于初始值设定项,其中每个作为赋值expression式的初始化子句都是一个常量expression式。 [注意:在这两种情况下,成员都可以出现在常量expression式中。 – 注意] 如果程序中使用了odr-use(3.2) ,并且名称空间作用域定义不包含初始值设定项,那么成员仍应在名称空间作用域中定义。
那么问题就变成这样了:
std::string str(baz);
答案是肯定的 ,所以我们也需要命名空间范围定义。
那么我们如何确定variables是否被使用 ? 第3.2
节[basic.def.odr]中的原始C ++ 11措辞说:
除非是未评估的操作数(第5章)或其子expression式,否则expression式可能被评估。 名称出现在潜在评估expression式中的variables是odr-use,除非它是满足出现在常量expression式 (5.19)中的要求并立即应用左值到右值转换(4.1)的对象 。
所以baz
确实产生了一个常数expression式,但是左值到右值的转换并不是立即应用的,因为baz
是一个数组。 这在第4.1
节[conv.lval]中有说明:
一个非函数非数组typesT的glvalue(3.10)可以转换为一个prvalue.53 […]
什么是应用在arrays到指针转换 。
[basic.def.odr]的这个措辞由于缺陷报告712而改变了,因为有些情况没有被这个措词覆盖,但是这些改变不会改变这种情况的结果。
这是C ++ 11中的一个缺陷 – 正如其他人所解释的那样,在C ++ 11中,一个静态的constexpr成员variables,不像其他types的constexpr全局variables,具有外部连接,因此必须在某处明确定义。
另外值得注意的是,在编译优化时,你可以经常在实践中脱离静态的constexpr成员variables,因为它们可以在所有用途中内联,但是如果你不经常优化编译,你的程序将无法链接。 这使得这是一个非常常见的隐藏陷阱 – 你的程序通过优化编译,但是一旦closures了优化(可能用于debugging),它就无法链接。
好消息虽然 – 这个缺陷是固定在C + + 17! 这个方法有点复杂:在C ++ 17中,静态的constexpr成员variables是隐式的内联的 。 内联应用于variables是C ++ 17中的一个新概念,但它实际上意味着它们不需要任何地方的显式定义。
是不是更优雅的解决scheme将char[]
更改为:
static constexpr char * baz = "quz";
这样我们可以在1行代码中定义/声明/初始化器。