关于静态常量数据成员的声明和定义的混淆

Enr*_*lis 24 c++ static variable-declaration language-lawyer

Scott Meyers 在 Effective Modern C++, Item 30 page 210 中写道,有

无需static const在类中定义完整的数据成员;仅声明就足够了,

那么示例代码是

class Widget {
  public:
    static const std::size_t MinVals = 28; // MinVals' declaration;
    ...
};
...                                        // no defn. for MinVals
std::vector<int> widgetData;
widgetData.reserve(Widget::MinVals);       // use of MinVals
Run Code Online (Sandbox Code Playgroud)

我确信这static const std::size_t MinVals = 28;既是声明是定义,因为它给了一个值MinVals,但评论似乎声称这只是一个声明;第二条评论实际上声称没有定义。代码后面的文字,确实是读

MinVals 缺乏定义。

这证实这static const std::size_t MinVals = 28;不是一个定义,所以我有点困惑。

cppreference对我帮助不大(我的粗斜体):

如果声明static了整型或枚举类型的数据成员(而不是),则可以使用初始化器对其进行初始化,其中每个表达式都是常量表达式,就在类定义中: constvolatile

struct X
{
   const static int n = 1;
   const static int m{2}; // since C++11
   const static int k;
};
const int X::k = 3;
Run Code Online (Sandbox Code Playgroud)

但是类中的前两行在我看来是定义。

以下 cppreference 示例也是如此:

struct X {
    static const int n = 1;
    static constexpr int m = 4;
};
const int *p = &X::n, *q = &X::m; // X::n and X::m are odr-used
const int X::n;             // … so a definition is necessary
constexpr int X::m;         // … (except for X::m in C++17)
Run Code Online (Sandbox Code Playgroud)

static const int n = 1;根据倒数第二个评论,我会说的是一个定义,但它不是。

Pio*_*cki 17

无需在类中定义完整的静态常量数据成员;仅声明就足够了,

仅当该对象未使用ODR时,单独声明就足够了,也就是说,如果数据成员未在需要其地址存在的上下文中使用(例如绑定到引用或应用运算符&)。初始化程序的存在等于定义。

在书中的示例中,很明显它MinVals不是 ODR 使用的,即编译器可以直接使用其值,而无需在内存中创建对象,因此语句:

widgetData.reserve(Widget::MinVals);
Run Code Online (Sandbox Code Playgroud)

变成:

widgetData.reserve(28);
Run Code Online (Sandbox Code Playgroud)

但是,如果在任何其他地方MinVals使用 ODR,就会使程序格式错误。

cppreference 中的所有其他示例都清楚地表明了何时使用 ODR 并且需要定义,何时不需要:

struct X
{
    const static int n = 1;
    const static int m{2}; // since C++11
    const static int k;
};
const int X::k = 3;
Run Code Online (Sandbox Code Playgroud)

nm是带有初始值设定项的声明。尝试获取nm应该失败的地址。

struct X {
    static const int n = 1;
    static constexpr int m = 4;
};
const int *p = &X::n, *q = &X::m;
const int X::n;
constexpr int X::m;
Run Code Online (Sandbox Code Playgroud)

表达式&X::n&X::m计数作为ODR用途的nm分别(即,地址被请求)。对于constexpr静态数据成员,在 C++17 之前需要定义。从 C++17 开始,static constexpr数据成员是隐式的inline,这意味着不需要类外定义,因为它们本身就是定义。