为什么我们需要为静态常量数据成员单独定义?

问题描述

你好,如果我有一个 static const 数据成员,那么我可以为它提供一个类内初始化器,我不需要在类主体之外再次定义它。

  • 但只有当该常量在类范围内使用时才是正确的,如果在外部使用,则必须在外部提供单独的定义,否则对它的任何引用都会导致链接错误:“对静态对象的未定义引用:x"。

      struct Foo{
          static int const sz_ = 100;
          std::array<int,sz_> ai_100{};
      };
    
      //int const Foo::sz_;
    
      int main(){
    
          float pts[Foo::sz_]{}; // ok
          void bar(int const&); // defined later on
          bar(Foo::sz_); // undefined reference to Foo::sz_
      }
    
      void bar(int const&){
          //do_something
      }
    
  • 为什么在类范围之外使用 static const 数据成员 sz_ 因为数组大小可以?

  • 为什么在将 sz 传递给函数 bar 时,该函数接受 const 的左值引用,但链接器无法链接并抱怨 Foo::sz_ 的定义?

  • 函数 bar 接受对 const int& 的左值引用,因此可以从 r 值对其进行初始化,因此为什么它与初始化器 Foo::sz_ 的定义有关?

解决方法

为什么我们需要为静态常量数据成员单独定义?

首先,我们必须记住一个定义规则。它实际上是一组规则,但我们在此上下文中感兴趣的规则的简化版本是:必须每个变量都只有一个定义

其次,我们应该考虑到类经常用于多个翻译单元。

如果静态成员变量的声明是一个定义,那么每个包含类定义的翻译单元都将包含该变量定义。这将与 ODR 相悖,链接器将不知道该怎么做。

因此,必须有一个单独的变量定义,允许将类定义包含在多个翻译单元中,同时将单独的变量定义保留在一个翻译单元中。


虽然过去的语言实现可能无法处理多个定义(在模板出现在 C++ 之前),但现在已经可以了,并且语言已经扩展(在 C++17 中)以允许内联定义的变量。 a b c id 0 4.20 2.0 0.0 0 1 3.01 3.0 0.0 1 2 2.10 4.0 0.0 1 3 1.20 5.0 0.0 1 4 0.52 6.0 0.0 1 5 0.02 6.0 1.0 1 6 5.30 7.0 0.4 0 7 4.20 8.0 0.0 1 8 3.10 9.0 0.0 1 9 2.40 10.0 0.0 1 10 1.30 11.0 0.0 1 11 0.20 12.0 0.0 1 12 5.98 13.0 0.0 0 13 4.23 14.0 0.3 1 14 3.33 15.0 0.0 1 15 2.11 16.0 0.0 1 16 1.30 17.0 0.0 1 17 0.30 18.0 0.0 1 18 5.50 13.0 0.0 0 关键字对于变量和函数具有相同的含义:它放宽了单一定义规则,允许(并且还要求)在每个 TU(使用 odr 的地方)中定义变量。

,

主要是由于历史原因。盯着 C++17,你可以有 inline data membersconstexpr 成员默认为 inline

,

您的非链接示例可以简化为:

struct Foo{
    static int const sz_ = 100;
};

int main(){
    int const *p = &Foo::sz_;   // undefined reference to Foo::sz_
}

换句话说,如果我们获取 Foo::sz_ 的地址,或者为其分配一个引用(这是一个非常相似的操作,在幕后),那么我们需要在某处定义 int const Foo::sz_;

这是为什么?好吧,如果我们只使用 Foo::sz_value,那么编译器不需要变量。它可以有效地将 100 用作文字。但是如果我们想要获取它的地址,那么我们需要一个实际的变量来引用。