32

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

#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。在类/结构之外定义静态整数时也很好(在这个简单的情况下是全局的)。但是,一旦它们像这样在里面,链接器就找不到它们。

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

4

6 回答 6

43

需要定义

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

#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);
}

可以在 c++中的 const and static specifiers 的类似问题中找到来自标准的引用

为什么有时代码在没有定义的情况下“工作”?

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

为什么您的源属于“链接器”类别是我看不到的,需要剖析 std::min 才能理解。注意:当我使用 GCC 在线尝试时,它工作正常,没有检测到错误。

替代方案:使用枚举

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

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

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

标准

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

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

9.4.2中的标准要求该定义:

C++03 措辞:

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

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

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

3.2 说以下关于 odr-use 的内容:

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

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

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

于 2011-02-03T20:07:02.343 回答
3

如果您只想要整数值,那么您也可以定义enum

#include <algorithm>

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

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

这绰绰有余。课外不需要声明!

在线演示:http ://www.ideone.com/oE9b5

于 2011-02-03T20:14:39.423 回答
2

这里有很好的答案,但要注意的另一件事是 的参数std::min()是引用,这需要传入变量的地址,并且由于这些变量不会进入编译单元的目标文件,链接器无法解析它们的地址。

您可能在未优化的构建中得到了这个,对吗?

我敢打赌,如果你启用优化,你不会用 gcc 得到这个。调用std::min()将被内联,引用将消失。

此外,如果您在调用 之前将Foo::A和分配给两个局部变量,这个问题也会消失。Foo::Bstd::min()

这并不理想,但如果您不拥有定义导致此问题的变量的代码,那么您可以考虑这样做。

于 2012-07-12T07:53:41.833 回答
2

您必须在类定义之外定义静态常量。

struct Foo {
    static const int A;
    static const int B;
};

const int Foo::A = 1;
const int Foo::B = 2;
于 2011-02-03T20:06:34.470 回答
1

看到你基本上使用 struct 作为命名空间,为什么不只使用命名空间:

#include <algorithm>

namespace Foo
{
    const int A = 1;
    const int B = 2;
};

int main()
{
    return std::min(Foo::A, Foo::B);
}
于 2011-02-04T02:18:58.597 回答
0

另一种解决方案是针对inline您的static变量,就像这样,它将在最终的翻译单元中可用,从而消除了未定义的符号错误。

请注意,这仅适用于 C++11 之后的 AFAIK。

于 2020-05-13T18:13:09.533 回答