结构/类中静态常量的奇怪未定义符号

要么我很累,要么发生了一些我不知道的奇怪事情,因为下面的代码导致链接时 Foo::A 和 Foo::B 的符号未定义.这在一个更大的项目中尽可能地最小化,但显示了我所看到的本质.

Either I'm very tired or something weird is happening that I'm not aware of, because the code below is resulting in undefined symbols for Foo::A and Foo::B when linking. This is minimized as much as I could from a larger project, but shows the essence of what I'm looking at.

#include <algorithm>

struct Foo
{
    static const int A = 1;
    static const int B = 2;
};

int main()
{
    return std::min(Foo::A, Foo::B);
}

没有 std::min 函数模板也可以正常工作,即只返回 Foo::A.在类/结构之外定义静态整数时也很好(在这个简单的情况下是全局的).但是,一旦它们像这样在里面,链接器就找不到它们.

Without the std::min function template it works fine, i.e. just return Foo::A. Also fine is when defining the static ints outside a class/struct (global in this simple case). However, as soon as they're inside like this, the linker cannot find them.

有人可以解释发生了什么吗?

Can someone explain what's happening?

推荐答案

需要定义

您提供的代码是非标准的.虽然您可以直接在类中为 const static int 成员提供初始化程序,但您仍然需要提供单独的定义.很奇怪,有点出乎意料,但你应该是这样写的:

Definition needed

The code you have provided is non-standard. While you can provide initializers for const static int members directly in the class, you still need to provide separate definitions. It is weird, a kind of unexpected, but you are expected to write it like this:

#include <algorithm>

struct Foo
{
    static const int A = 1;
    static const int B = 2;
};

const int Foo::A;
const int Foo::B;

int main()
{
    return std::min(Foo::A, Foo::B);
}

可以在 const andc++中的静态说明符

至于为什么即使不提供定义也经常可以绕过:如果仅在常量表达式中使用这些成员,编译器将始终直接解析它们,并且链接器解析将没有访问权限.只有当您以某种方式使用它时编译器无法直接处理,并且只有在这种情况下,链接器才会检测到符号未定义.我猜这可能是 Visual Studio 编译器中的一个错误,但考虑到该错误的性质,我怀疑它是否会被修复.

As for why you can often get around even without providing the definition: if you are using those members only in constant expressions, compiler will always resolve them directly and there will be no access left for linker resolution. It is only when you use it in some way which cannot be handled by compiler directly, and only in such case the linker will detect the symbol is undefined. I guess this is probably a bug in the Visual Studio compiler, but given the nature of the bug I doubt it will be ever fixed.

为什么您的源代码属于链接器"类别是我看不到的,需要剖析 std::min 才能理解这一点.注意:当我尝试 online with GCC 时,它起作用了,但没有检测到错误.

Why your source falls into the "linker" category is something I do not see, one would need to dissect the std::min to understand that. Note: When I have tried it online with GCC, it worked, the error was not detected.

另一种选择是使用枚举.当您遇到不支持静态 const int 内联"初始化程序的旧编译器(例如 Visual Studio 6)时,此版本也可以派上用场.但是请注意,使用 std::min 您会遇到枚举的其他问题,您需要使用显式实例化或强制转换,或者将 A 和 B 放在一个命名枚举中,如 纳瓦兹的答案:

Another alternative is to use enum. This version can also come handy when you hit an old compiler which does not support static const int "inline" initializers (such as was Visual Studio 6). Note however that with std::min you are hitting other problems with enums and you need to use an explicit instantiation or casting, or have both A and B in one named enum as in the answer by Nawaz:

struct Foo
{
    enum {A = 1};
    enum {B = 2};
};

int main()
{
    return std::min<int>(Foo::A, Foo::B);
}

标准

注意:即使 Stroustrup C++ FAQ 也会出错并且不需要与标准一样严格的定义:

Standards

Note: even Stroustrup C++ FAQ gets this wrong and does not require the definition as strictly as the standard does:

当(且仅当)静态成员具有类外定义时,您可以获取静态成员的地址

You can take the address of a static member if (and only if) it has an out-of-class definition

9.4.2 中的标准要求定义:

The definition is required by a standard in 9.4.2:

C++03 措辞:

如果在程序中使用该成员,则该成员仍应在名称空间范围内定义,并且名称空间范围定义不应包含初始化程序

The member shall still be defined in a name-space scope if it is used in the program and the namespace scope definition shall not contain an initializer

9.4.2 的 C++11 措辞有点不同:

C++11 wording of 9.4.2 is a bit different:

3 如果该成员在程序中被 odr-used (3.2),则仍应在命名空间范围内定义

3 The member shall still be de?ned in a namespace scope if it is odr-used (3.2) in the program

3.2 关于 odr-use 的说明如下:

3.2 says following about odr-use:

3 变量 x 其名称显示为潜在求值表达式 ex 是 odr-used 除非 x 是满足出现在常量表达式 (5.19) 中的要求并且 ex 是表达式 e 的潜在结果集的元素,其中左值到右值转换 (4.1) 应用于 e,或者 e 是丢弃值表达式(第 5 条).

3 A variable x whose name appears as a potentially-evaluated expression ex is odr-used unless x is an object that satis?es the requirements for appearing in a constant expression (5.19) and ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (4.1) is applied to e, or e is a discarded-value expression (Clause 5).

4 每个程序都应包含该程序中 odr 使用的每个非内联函数或变量的一个定义;无需诊断.

4 Every program shall contain exactly one de?nition of every non-inline function or variable that is odr-used in that program; no diagnostic required.

我不得不承认我不确定 C++11 措辞的确切含义是什么,因为我无法理解 odr 使用规则.

I have to admit I am not sure what the exact implications of C++11 wording are, as I fail to understand the odr-use rules.

相关文章