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

Confusion about declaration and definition of static const data memebers

本文关键字:声明 定义 数据 于静态 静态 常量      更新时间:2023-10-16

斯科特·迈耶斯(Scott Meyers(在《有效的现代C++》(第30项,第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

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

MinVals缺乏定义。

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

CPP偏好对我没有多大帮助(我的粗斜体(:

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

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

但类中的前两行对我来说看起来是定义的。

以下关于 cpp 首选项的示例也是如此:

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;是一个定义,但事实并非如此,基于倒数第二条评论。

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

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

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

widgetData.reserve(Widget::MinVals);

成为:

widgetData.reserve(28);

但是,如果在任何其他位置使用ODRMinVals,这将使程序格式不正确。

来自 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的,这意味着不需要类外定义,因为它们本身就是定义。

查看此标准草案,您的示例似乎属于灰色区域。虽然没有明确提及以下行:

static const std::size_t MinVals = 28;

给出一个非常相似的例子:

6.1 声明和定义
...
2 声明是一个定义,除非
...
2.3 — 它在类定义
中声明了一个非内联静态数据成员...
示例:除以下一项外,其他所有定义都是:

int a;//定义一个
外部常量 int c = 1;//定义 c
...

第二个示例与您的代码很接近,但在具有extern限定符方面存在显着差异。另外,请注意,上述声明(默认情况下(也是一个定义,除非列出的条件之一适用;我想说(虽然我不是语言律师(在你的案件中,这些条件都没有完全满足,所以你的声明也是一个定义。

注意:链接的文档只是一个标准草案;请务必阅读其第一页底部的"免责声明"!

摘自标准章节"12.2.3.2 静态数据成员":

如果在程序中使用 odr 时,仍应在命名空间作用域中定义该成员,并且命名空间作用域定义不应包含初始值设定项。

通过使用它,应对其进行定义。