static const 数据成员的声明和定义的困惑

Confusion about declaration and definition of static const data memebers

Scott Meyers 在 Effective Modern C++(第 30 页第 210 页)中写道

no need to define integral static const data members in classes; declarations alone suffice,

则示例代码为

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

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

MinVals lacks a definition.

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

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

If a static data member of integral or enumeration type is declared const (and not volatile), it can be initialized with an initializer in which every expression is a constant expression, right inside the class definition:

struct X
{
   const static int n = 1;
   const static int m{2}; // since C++11
   const static int k;
};
const int X::k = 3;

但 class 中的前两行对我来说是定义。

以下关于 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)

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

查看此 Draft Standard,您的示例似乎落入了灰色区域。虽然没有明确提及以下行:

    static const std::size_t MinVals = 28;

给出的例子非常相似:

6.1 Declarations and definitions
...
2 A declaration is a definition unless
...
2.3 — it declares a non-inline static data member in a class definition
...
Example: All but one of the following are definitions:

int a; // defines a
extern const int c = 1; // defines c
...

第二个示例与您的代码 接近 ,但在 extern 限定符方面有显着差异。另外,请注意,除非列出的条件之一适用,否则上面声明的声明(默认情况下)也是 definition;我会说(虽然我不是语言律师)在你的情况下none 完全,所以你的声明也是一个定义。

注意:链接文档只是一个草案标准;请务必阅读第一页底部给出的 'disclaimer'!

no need to define integral static const data members in classes; declarations alone suffice,

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

在书中的示例中,很明显 MinVals 未使用 ODR,即编译器可以直接使用它的值,而不必在内存中创建对象,因此声明:

widgetData.reserve(Widget::MinVals);

变成:

widgetData.reserve(28);

但是,如果在任何其他地方 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;

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;

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

来自The Standard章节“12.2.3.2静态数据成员”:

The member shall still be defined in a namespace scope if it is odr-used in the program and the namespace scope definition shall not contain an initializer.

使用它,它应该被定义。