【问题标题】:Why do non-const, non-int/enum static data members have to be initialized outside the definition?为什么非 const、非 int/enum 静态数据成员必须在定义之外进行初始化?
【发布时间】:2013-03-01 21:34:24
【问题描述】:

我知道只有静态、常量和 int/enum (pre c++11) 的数据成员才能在类声明中初始化。 “所有其他静态数据成员必须在全局命名空间范围内定义(即在类定义的主体之外),并且只能在这些定义中初始化”。

为什么不能在类定义中初始化其他静态数据成员?禁止这样做是否有特定原因?

如果数据成员是特定于类的,为什么它们在全局命名空间范围内声明,而不是在与其类相关的某个范围内声明?

【问题讨论】:

  • “全局命名空间范围”具有误导性——定义发生在实际类的命名空间范围内;不是全局命名空间的范围。 (正如我在回答中所展示的)

标签: c++ static header


【解决方案1】:

为什么不能在类定义中初始化其他静态数据成员?禁止这样做是否有特定原因?

很可能是因为 C++ 有单独的翻译单元。编译器需要选择一个目标文件,用于放置这些符号的初始化逻辑。将其强制放在特定的源文件中可以让编译器轻松做出决定。

如果数据成员特定于类,为什么它们在全局命名空间范围内声明,而不是在与其类相关的某个范围内声明?

因为这正是 C++ 处理类成员的方式。这与成员函数等其他类成员没有什么不同:

头文件:

namespace example {

// Class declared in header
struct some_class
{
    // Member variable
    static float example;
    // Member function
    void DoStuff() const;
};

}

源文件:

namespace example {

    // Implement member variable
    float some_class::example = 3.14159;
    // Implement member function
    void some_class::DoStuff() const
    {
         //....
    }
}

允许在头文件中初始化静态 const 整数成员有一个特定的例外,因为它允许编译器将它们视为编译时常量。也就是说,您可以使用它们来定义数组的大小或类定义中的其他类似位。

【讨论】:

  • 我认为你的最后一段是我第一个问题的答案?另外,您是说“全局命名空间范围”实际上是指相关类/结构的“全局命名空间范围”吗?这不是真正的 GLOBAL 命名空间....?
  • @user:是的。最后的注释“(即在类定义的主体之外)”正在理解这真正意味着什么——只是需要在某个地方有一个定义。
【解决方案2】:

为什么不能在类定义中初始化其他静态数据成员?禁止这样做是否有特定原因?

通常,所有静态对象都需要在一个翻译单元中进行定义,以便它们具有明确定义的地址。作为一个特殊的例外,静态、常量、非易失类成员不需要定义如果它们的地址不是必需的,并且它们具有足够简单的类型,它们的值可以被编译器替换-时间常数。

历史上,“足够简单”被定义为整数或枚举类型; C++11 将其扩展为包含任何带有 constexpr 说明符的文字类型。

如果数据成员特定于类,为什么它们在全局命名空间范围内声明,而不是在与其类相关的某个范围内声明?

它们没有在全局命名空间范围内声明。它们在类中声明和作用域。

如果你的意思是,为什么它们定义在类定义之外,那是因为在整个程序中静态成员必须只有一个定义;但是这个类必须在每个使用它的翻译单元中定义。

【讨论】:

  • +1。当然,他们为inline 函数(您可以获取地址)找到了一些东西,并且推测他们可以为此使用相同的机制。
  • @BillyONEal:确实可以。但他们没有。
【解决方案3】:

为什么不能在类中初始化其他静态数据成员 定义?禁止这样做是否有特定原因?

静态数据成员在许多方面(尤其是从编译器的角度来看)类似于具有外部链接的命名空间范围数据对象。

静态数据成员的声明只是一个声明,而不是一个定义。它类似于全局对象的extern 声明,必须包含在可以使用该对象的任何翻译单元中。

定义必须恰好出现在一个翻译单元中,这是初始化表达式所属的地方。除非表达式满足常量表达式的严格标准,否则它的值很可能取决于调用它的时间和上下文。在多个翻译单元中出现这样的初始化表达式会使初始化的执行上下文和时间以及最终的初始值不明确。

一个类范围的编译时常量被认为足够有价值,可以为某些类型的常量静态成员(然后可用于初始化枚举或指定数组维度等)进行例外处理。使用常量表达式至少更难在不同的翻译单元中意外地产生不同的初始化器值。这个概念在 C++11 中通过constexpr 成员进行了扩展。

如果数据成员特定于类,为什么要声明它们 在全局命名空间范围内,而不是与其相关的某个范围 上课?

声明在类范围内。非定义声明实际上是在类定义中,并且定义出现在命名空间范围内,就像类成员的任何其他类外定义一样。成员名由类名限定,所以明确表示为类的成员,初始化表达式实际上被认为是在类的范围内(至少在C++11;我没有C+ +98/03 标准在这里可用)。

【讨论】:

  • “翻译单元”是编译器编译的一个单元。通常这是一个单一的“x.cpp”文件。头文件通过#include 指令(递归)包含在翻译单元中。因此,“class.h”的内容是包含该文件的每个翻译单元的一部分(直接或间接)。 “class.cpp”的内容(通常)只出现在一个翻译单元中。
  • @user: N3485 2.1 [lex.seperate]/1:程序的文本保存在本国际标准中称为源文件的单元中。源文件连同所有头文件 (17.6.1.2) 和通过预处理指令 #include 包含的源文件 (16.2),减去由任何条件包含 (16.1) 预处理指令跳过的任何源代码行,称为翻译单元。 [ 注意:一个 C++ 程序不需要同时翻译。 ——尾注]
  • 翻译单元是否包含包含的头文件的源文件?所以如果我有包含 y.h 的 x.cpp,y.cpp 也会在翻译单元中吗?
  • @user: 如果是#included.
  • @user997122 没有。除非它本身包含在内。实际上,C++ 中没有头文件的 源文件之类的东西。您可以在没有任何关联源文件的情况下标头文件,而其他文件的定义分布在多个源文件中。您甚至可以手动将声明的副本粘贴到每个源文件中,而不是从头文件中#includeing 它们。并且名称是任意的。 “y.cpp”与“y.h”相关只是一个约定。
【解决方案4】:

你必须反过来看。基本上,静态数据成员必须在源文件中的类定义之外定义和初始化。 static const int 有一个例外,因为它避免了定义成员数组大小的各种丑陋的变通方法。

【讨论】:

    【解决方案5】:

    每次实例化类时都会重新初始化它们。每次创建 Foo 类型的新对象时,所有 Foo 的静态变量都会重置为其初始值,这可能不是您想要的。因此,如果您想在对象中使用静态变量,它们要么 a) 无法更改其值,这意味着将它们重新初始化为相同的值是安全的,或者 b) 只能在初始化函数的上下文之外进行更改。

    【讨论】:

    • 这没有意义。为什么每个实例都实例化时会发生静态成员的初始化?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-04
    • 1970-01-01
    • 2018-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-04
    相关资源
    最近更新 更多