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

C++ 结构/类中静态常量的奇怪的未定义符号,c++,initialization,definition,static-members,undefined-symbol,C++,Initialization,Definition,Static Members,Undefined Symbol,要么我很累,要么我不知道发生了什么奇怪的事情,因为下面的代码是导致链接时未定义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); } #包括 结构Foo

要么我很累,要么我不知道发生了什么奇怪的事情,因为下面的代码是导致链接时未定义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);
}
#包括
结构Foo
{
静态常数INTA=1;
静态常数int B=2;
};
int main()
{
返回std::min(Foo::A,Foo::B);
}
如果没有std::min函数模板,它可以正常工作,即只返回Foo::A。在类/结构外部定义静态int时也可以正常工作(在这个简单的例子中是全局的)。然而,一旦他们像这样在里面,链接器就找不到他们


有人能解释发生了什么吗?

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

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

const int Foo::A = 1;
const int Foo::B = 2;
需要定义 您提供的代码是非标准的。虽然可以直接在类中为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);
}
#包括

为什么有时代码“工作”时没有定义? 至于为什么即使不提供定义也可以到处走动:如果您仅在常量表达式中使用这些成员,编译器将始终直接解析它们,并且没有链接器解析的访问权限。只有以编译器无法直接处理的方式使用它,并且只有在这种情况下,链接器才会检测到符号未定义。我想这可能是VisualStudio编译器中的一个bug,但鉴于bug的性质,我怀疑它是否会被修复

为什么您的源代码属于“链接器”类别是我没有看到的,您需要剖析std::min才能理解这一点。注意:当我尝试它时,它工作了,没有检测到错误

备选方案:使用枚举 另一种选择是使用enum。当您遇到不支持静态const int“inline”初始值设定项的旧编译器(如was Visual Studio 6)时,此版本也很方便。但是请注意,使用std::min时,枚举会遇到其他问题,需要使用显式实例化或强制转换,或者在一个命名的枚举中同时使用A和B,如中所示:

structfoo
{
枚举{A=1};
枚举{B=2};
};
int main()
{
返回std::min(Foo::A,Foo::B);
}
标准 注:即使是错误的,也不需要像标准那样严格地定义:

如果(且仅当)静态成员具有类外定义,则可以获取其地址

定义是第9.4.2节标准所要求的:

C++03措辞:

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

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

3如果在程序中使用odr(3.2),则仍应在名称空间范围内定义成员

3.2关于odr的使用说明如下:

3除非x是一个满足常量表达式(5.19)中出现要求的对象,且ex是表达式e的一组潜在结果的一个元素,其中左值到右值的转换(4.1)应用于e,否则使用的变量x的名称为潜在评估表达式ex,或者e是一个废弃的值表达式(第5条)

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


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

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

#include <algorithm>

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

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

#include

既然您基本上是使用struct作为名称空间,为什么不直接使用名称空间:

#include <algorithm>

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

int main()
{
    return std::min(Foo::A, Foo::B);
}
#包括
名称空间Foo
{
常数INTA=1;
常数int B=2;
};
int main()
{
返回std::min(Foo::A,Foo::B);
}

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

你可能在一个未优化的版本中得到这个,对吗

我敢打赌,如果您启用优化,您将无法通过gcc获得这一点。对
std::min()
的调用将被内联,引用将消失

另外,如果在调用
std::min()
之前将
Foo::A
Foo::B
分配给两个局部变量,那么这个问题也会消失


这不是理想的,但是如果你不拥有定义导致这个问题的变量的代码,那么这是你可以考虑的。

< P>另一个解决方案是:<代码>内联 >你的代码>静态< /代码>变量,这样,它将在最终的翻译单元中提供,消除了未定义的符号错误


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

如果它是
const
,他也可以在内部声明它。我不知道整数常量需要这个。我经常看到这种代码,不一定是我写的;)这是否意味着永远不应该像我所展示的那样,总是在cpp文件中初始化静态常量?请参阅此处了解更多信息:您可以在.hpp(类中)中初始化它们,但仍然应该提供单独的定义。请参阅添加的链接。链接器错误可能是因为
std::min
函数通过引用获取它的参数。因此,编译器必须记录使用了常量的地址(即使函数是inli,也可能是这样)
#include <algorithm>

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

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