静态Const成员初始化和模板(与静态功能相比) - 这是如何工作的?

时间:2012-07-04 22:55:27

标签: c++ compilation static-members

我知道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

上发布了一些最小代码

2 个答案:

答案 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的地址,这需要定义。