对静态constexpr char[]的未定义引用
Undefined reference to static constexpr char[]
我想在我的类中有一个static const
char
数组。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修复了constexpr static
成员变量的这个问题,如果它被频繁使用,需要一个脱行定义。有关c++ 17之前版本的详细信息,请参阅本回答的后半部分。
提案P0386内联变量引入了将 inline
说明符应用于变量的能力。特别是在这种情况下,constexpr
意味着静态成员变量的inline
。提案说:
内联说明符既可以应用于函数,也可以应用于变量。声明的变量Inline与声明为Inline的函数具有相同的语义:它可以在多个翻译单元,必须在使用它的每个翻译单元中定义,并且程序的行为就好像只有一个变量。
和修改[basic.def]p2:
声明就是定义,除非
…
- 它在类定义之外声明了一个静态数据成员,并且变量是在类中使用constexpr说明符定义的(这种用法已弃用;参见[depr.static_constexpr]),
…
and add [depr.static_constexpr]:
为了与先前的c++国际标准兼容,一个constexpr静态数据成员可以在类之外冗余地重新声明没有初始化式。不赞成这种用法。(例子:
struct A { static constexpr int n = 5; // definition (declaration in C++ 2014) }; constexpr int A::n; // redundant declaration (definition in C++ 2014)
- end示例]
c++ 14及更早版本
在c++ 03中,我们只允许为const整型或const枚举类型提供类内初始化式,在c++ 11中使用 constexpr
扩展到文字类型。
在c++ 11中,如果静态constexpr
成员不是odr使用的,我们不需要为它提供命名空间作用域定义,我们可以从c++ 11标准草案9.4.2
[class.static]小节中看到这一点。数据]上面写着(强调我的前进):
[…可以在类中声明文字类型的静态数据成员带有constexpr说明符的定义;如果是这样,其声明应指定一个大括号或等号初始化项,其中每个初始化项子句这是一个赋值表达式,是一个常量表达式。[注:在在这两种情况下,成员都可以出现在常量表达式中。端请注意)如果成员在程序和命名空间作用域定义中被odr (3.2)使用,则该成员仍应定义在命名空间作用域中不能包含初始化式。
那么问题就变成了,baz
是否在这里使用
std::string str(baz);
,答案是是,所以我们还需要一个命名空间作用域定义。
那么我们如何确定一个变量是否是常用的呢?原始c++ 11在3.2
[basic.def.odr]章节中的措辞是:
除非表达式是未求值的,否则它可能被求值操作数(第5条)或其子表达式。其名称为是不常用的,除非它是满足出现在a中的要求的对象常量表达式(5.19)和左值到右值的转换(4.1)立即应用。
因此baz
确实产生常量表达式,但左值到右值转换不会立即应用,因为它不适用,因为baz
是一个数组。这在4.1
[conv]小节中有介绍。表示:
非函数、非数组类型T的glvalue(3.10)可以是转换为右值。53[…]
数组到指针的转换应用了什么?
[basic.def.odr]的措辞是由于缺陷报告712而改变的,因为一些情况没有被这种措辞所覆盖,但是这些变化不会改变这种情况的结果。
这确实是c++ 11中的一个缺陷——正如其他人解释的那样,在c++ 11中,静态constexpr成员变量与其他类型的constexpr全局变量不同,具有外部链接,因此必须在某处显式定义。
同样值得注意的是,在使用优化进行编译时,您通常可以在实践中避免使用没有定义的静态constexpr成员变量,因为它们最终会在所有使用中被内联,但是如果您在不进行优化的情况下进行编译,您的程序通常会链接失败。这使得这成为一个非常常见的隐藏陷阱——你的程序在优化后编译得很好,但一旦你关闭优化(可能是为了调试),它就无法链接。
好消息-这个缺陷在c++ 17中被修复了!不过这种方法有点复杂:在c++ 17中,静态constexpr成员变量是隐式内联的。将内联应用于变量是c++ 17中的一个新概念,但它实际上意味着它们不需要在任何地方显式定义。
对于静态成员的外部链接,我的解决方法是使用constexpr
引用成员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]'
更优雅的解决方案不是将char[]
改为:
static constexpr char * baz = "quz";
通过这种方式,我们可以在一行代码中拥有定义/声明/初始化式。
在我的环境中,gcc版本是5.4.0。添加"-O2"可以修复此编译错误。在请求优化时,gcc似乎可以处理这种情况。
- 对C宏的未定义引用,但在定义它时会出现重新定义错误
- 编译时的 CImg 库返回对"__imp_SetDIBitsToDevice"的未定义引用
- 对Py_Initialize()的未定义引用
- 使用mysql c++连接器的未定义引用
- 对 Scalar ::Scalar() 的未定义引用
- 对复制 CTOR 和 CTOR 的未定义引用
- 对显式实例化的模板函数的未定义引用
- TensorRT (C++ API) 对"createNvOnnxParser_INTERNAL"的未定义引用
- 2个模板化类的非模板友元函数未定义引用错误
- 编译 libfluid 样本控制器时对"event_base_del_virtual"的未定义引用
- 获取对function_name的未定义引用
- 对 'std::thread::_M_start_thread CMake 的未定义引用进行基准测试
- 对结构方法的未定义引用
- 使用内联函数 c++ 的未定义引用
- 对 CMake 中'cudaRegisterLinkedBinary'链接错误的未定义引用?
- 对 DLOPEN 的未定义引用
- QT C++中对全局变量的未定义引用
- 快速数学导致对"__pow_finite"的未定义引用
- 对 boost::system::d etail::system_category_instance 的未定义引用,从
- OpenCV 3.4.3 中对 'cv::String::d eallocate()' 错误的未定义引用