我知道C ++标准说(第9.4.2段第4段),整数或枚举类型的静态成员变量可以在类中提供初始化器,但是这需要在类外部定义该成员(在编译单位)。即,你需要做这样的事情:
class A
{
public:
static const int X = 10;
};
// this is required by the standard
const int A::X;
我已经看过(我已经看过它说过其他地方)有些编译器可以让你在没有外部定义的情况下逃脱。这适用于OS X上的gcc 4.2.1:
#include <iostream>
class A
{
public:
static const int X = 10;
};
int main(int argc, char** argv)
{
std::cout << A::X << std::endl;
return 0;
}
我最近遇到了一个人做过这个的错误,但他们在模板化函数中使用了成员变量(std::max
确切),并且它不会编译,抱怨未定义的符号A :: X。即,这不起作用:
#include <iostream>
#include <algorithm>
class A
{
public:
static const int X = 10;
};
int main(int argc, char** argv)
{
std::cout << std::max(1, A::X) << std::endl;
return 0;
}
在类外定义中添加它可以使它工作。
这是一个学术问题,但我想知道为什么会发生这种情况。特别是关于如果我们用静态函数替换静态成员变量(static const int X = 10;
变为static int X()
,A::X
变为A::X()
),那么它将在没有课外定义。我提到模板的原因是因为std::max
是模板化的,而其他模板化函数会重现相同的行为。它可能与模板没有特别的关系,但我想了解为什么模板会导致它们的行为。我认为这必须与模板和静态成员编译/实现的方式有关吗?
PS - 我在github
上发布了一些最小代码答案 0 :(得分:3)
它将在没有定义的情况下编译。
如果静态成员变量是 odr-used ,则在链接时需要定义。 (如果编译器在每次引用时都设法替换它的实际值,那就不会是 odr-used 。另一方面,取其地址肯定会使它 odr-used )
这是完整的规则(第9.4.2节[class.static.data]
):
如果非易失性
const static
数据成员是整数或枚举类型,则其在类定义中的声明可以指定大括号或等于初始值,其中每个 initializer-clause 是赋值表达式是一个常量表达式。可以使用static
说明符在类定义中声明文字类型的constexpr
数据成员;如果是这样,它的声明应指定一个大括号或等于初始化,其中作为赋值表达式的每个 initializer-clause 是一个不断表达。 [注意:在这两种情况下,成员可能会出现在常量表达式中。 - 结束注释] 如果在程序中使用了odr,并且命名空间范围定义不包含初始值设定项,则仍应在命名空间范围内定义该成员。
和第3.2节[basic.def.odr]
:
名称显示为可能评估的表达式的变量是 odr-used ,除非它是 满足出现在常量表达式中的要求的对象,并立即应用左值到右值的转换。
出现在常量表达式中的要求得到满足,因此一切都取决于它是用作左值还是 rvalue 。
std::max
采用左值引用,因此没有立即左值到右值的转换。
与模板的唯一交互是可能有多个等效定义,链接器将选择任何一个。在您的情况下,没有模板,因此多个定义将产生“符号乘法定义”类型的错误。
当您忘记提供定义时,两种情况(模板类的成员和普通类的成员)都会给出相同的错误:“undefined symbol”。
答案 1 :(得分:2)
我们来看你的第一个例子:
std::cout << A::X << std::endl;
由于A::X
的类型为const int
,因此会调用cout.operator<<(int value)
。请注意,在此次调用中int value
由值执行。我相信编译器执行constant folding并只用值替换A::X
。但是,在C ++ 03中执行此操作不需要。但是,在C ++ 11中,规则已经改变,这引用了这个引用:unless it is an object that satisfies the requirements for appearing in a constant expression and the lvalue-to-rvalue conversion is immediately applied.
(如Ben Voigt所述)。
现在让我们看一下std::max(1, A::X)
的定义:
template< class T >
const T& max( const T& a, const T& b );
std::max
函数通过 reference 获取它的参数,换句话说,必须知道A::X
的地址才能完成调用。必须知道A::x
的地址,这需要定义。