C++ 对静态constexpr char[]的未定义引用
我想在我的类中有一个C++ 对静态constexpr char[]的未定义引用,c++,c++11,static-members,constexpr,C++,C++11,Static Members,Constexpr,我想在我的类中有一个静态常量字符数组。GCC抱怨并告诉我应该使用constexpr,尽管现在它告诉我这是一个未定义的引用。如果我使数组成为非成员,那么它将编译。发生了什么事 // .hpp struct foo { void bar(); static constexpr char baz[] = "quz"; }; // .cpp void foo::bar() { std::string str(baz); // undefined reference to baz } 添加
静态常量
字符
数组。GCC抱怨并告诉我应该使用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++17引入了内联变量
C++17修复了constepr static
成员变量的此问题,如果使用odr,则需要一个越界定义。有关C++17之前版本的详细信息,请参见此答案的后半部分
提案引入了将内联
说明符应用于变量的能力。特别是在这种情况下,constepr
意味着inline
用于静态成员变量。提案说:
内联说明符既可以应用于函数,也可以应用于变量。声明的变量
内联与内联声明的函数具有相同的语义:它可以在
必须在使用odr的每个翻译单元中定义多个翻译单元,以及
程序的行为就像只有一个变量一样
并修改了[basic.def]p2:
除非- 它在类定义之外声明了一个静态数据成员,并且该变量是在类中使用constexpr说明符定义的(不推荐使用此用法;请参见[depr.static_constexpr])
struct A {
static constexpr int n = 5; // definition (declaration in C++ 2014)
};
constexpr int A::n; // redundant declaration (definition in C++ 2014)
— 结束示例 ]
C++14及更早版本 在C++03中,我们只允许为常量积分或常量枚举类型提供类内初始值设定项,在C++11中,使用
constexpr
将其扩展到文本类型
在C++11中,我们不需要为静态constepr
成员提供名称空间范围定义,如果它没有使用odr,我们可以从草案C++11标准部分9.4.2
[class.static.data]中看到这一点,其中说(重点放在后面):
[…]可以在类中声明文本类型的静态数据成员
使用constexpr说明符定义;如果是,其声明应
指定一个大括号或相等的初始值设定项,其中包含每个初始值设定项子句
这是一个赋值表达式,它是一个常量表达式。[注:在
在这两种情况下,成员都可能出现在常量表达式中。-结束
注]
如果在程序和命名空间范围定义中使用了odr(3.2),则仍应在命名空间范围中定义成员
不应包含初始值设定项
那么问题就变成了,baz
odr是否在这里使用:
答案是肯定的,因此我们还需要一个名称空间范围定义
那么,我们如何确定是否使用了odr变量呢?3.2节[basic.def.odr]中最初的C++11措辞说:
除非表达式是未计算的表达式,否则可能会对其进行计算
操作数(第5条)或其子表达式。名称为
除非
它是一个满足出现在场景中的要求的对象
常量表达式(5.19)和左值到右值的转换
(4.1)立即应用
因此,baz
确实会产生一个常量表达式,但由于baz
是一个数组,因此不会立即应用左值到右值的转换。这在第4.1节
[conv.lval]中有介绍,其中说:
非函数、非数组类型T的glvalue(3.10)可以是
转换为prvalue.53[…]
数组到指针转换中应用的内容
[basic.def.odr]的这一措辞由于以下原因而发生了变化,因为该措辞未涵盖某些情况,但这些变化不会改变这种情况的结果。将字符[]
更改为:
static constexpr char * baz = "quz";
通过这种方式,我们可以在一行代码中使用定义/声明/初始值设定项。这确实是C++11中的一个缺陷-正如其他人所解释的,在C++11中,静态constexpr成员变量与其他类型的constexpr全局变量不同,具有外部链接,因此必须在某个地方显式定义
还值得注意的是,在使用优化编译时,您通常可以在实践中避开没有定义的静态constexpr成员变量,因为它们最终可能在所有用途中都是内联的,但是如果您在没有优化的情况下编译,您的程序通常将无法链接。这使得这成为一个非常常见的隐藏陷阱-您的程序编译fin使用优化,但一旦关闭优化(可能是为了调试),它就无法链接
好消息是——这个缺陷在C++17中得到了修复!不过这种方法有点复杂:在C++17中,静态constepr成员变量。have在C++17中是一个新概念,但实际上意味着它们不需要任何明确的定义。我的静态成员外部链接解决方法是使用constepr
引用成员getter(不会遇到@gnzlbg作为对@deddebme答案的注释而提出的问题)。
这个习惯用法对我来说很重要,因为我讨厌在我的项目中有多个.cpp文件,并尝试将数量限制为一个,它只包含\include
s和main()
函数
// foo.hpp
struct foo {
static constexpr auto& baz() { return "quz"; }
};
// some.cpp
auto sz = sizeof(foo::baz()); // sz == 4
auto& foo_baz = foo::baz(); // note auto& not auto
auto sz2 = sizeof(foo_baz); // 4
auto name = typeid(foo_baz).name(); // something like 'char const[4]'
在我的环境中,gcc vesion是5.4.0。添加“-O2”可以修复此编译错误
// foo.hpp
struct foo {
static constexpr auto& baz() { return "quz"; }
};
// some.cpp
auto sz = sizeof(foo::baz()); // sz == 4
auto& foo_baz = foo::baz(); // note auto& not auto
auto sz2 = sizeof(foo_baz); // 4
auto name = typeid(foo_baz).name(); // something like 'char const[4]'