3

我知道 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::XIe,这不起作用:

#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上发布了一些最小的代码

4

2 回答 2

3

它将在没有定义的情况下编译。

如果静态成员变量是odr-used,则在链接时需要定义。(如果编译器每次引用它时都设法替换它的实际值,它就不会被odr-used 使用。另一方面,获取它的地址肯定会使其成为odr-used

这是完整的规则(第 9.4.2 节[class.static.data]):

如果非易失性const static数据成员是整数或枚举类型,则其在类定义中的声明可以指定一个大括号或等式初始化器,其中 作为赋值表达式的每个初始化器子句都是一个常量表达式。字面量类型的数据成员可以用说明符在类定义中声明;如果是这样,它的声明应指定一个大括号或等式初始化器,其中作为赋值表达式的每个初始化器子句都是一个常量表达式。[ 注意:在这两种情况下,成员都可能出现在常量表达式中。——尾注]staticconstexpr如果该成员在程序中被 odr-used 并且命名空间范围定义不应包含初始化程序,则该成员仍应在命名空间范围内定义。

并从第 3.2 节[basic.def.odr]

名称显示为潜在求值表达式的变量是odr-used的,除非它是一个满足出现在常量表达式中的要求并且立即应用左值到右值转换的对象。

满足出现在常量表达式中的要求,所以一切都取决于它是用作左值还是右值

std::max接受左值引用,因此不会立即进行左值到右值的转换。


与模板的唯一交互是可能有多个等效定义,链接器将选择任何一个。在您的情况下,没有模板,因此多个定义会产生“符号多重定义”类型的错误。

当您忘记提供定义时,两种情况(模板类成员和普通类成员)都会给出相同的错误:“未定义符号”。

于 2012-07-04T23:06:50.890 回答
2

让我们以您的第一个示例为例:

std::cout << A::X << std::endl;

因为A::X是类型const int,所以调用cout.operator<<(int value)。请注意,在此调用int value中是按获取的。我相信编译器会执行常量折叠,只是用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函数通过引用获取它的参数,换句话说,A::X必须知道的地址才能完成调用。A::x必须知道的地址,这需要定义。

于 2012-07-04T23:12:33.903 回答