【问题标题】:Undefined reference to static class member对静态类成员的未定义引用
【发布时间】:2021-07-10 21:18:58
【问题描述】:

谁能解释为什么下面的代码不能编译?至少在 g++ 4.2.4 上。

更有趣的是,为什么当我将 MEMBER 转换为 int 时它会编译?

#include <vector>

class Foo {  
public:  
    static const int MEMBER = 1;  
};

int main(){  
    vector<int> v;  
    v.push_back( Foo::MEMBER );       // undefined reference to `Foo::MEMBER'
    v.push_back( (int) Foo::MEMBER ); // OK  
    return 0;
}

【问题讨论】:

  • 我编辑了问题以将代码缩进四个空格,而不是使用
     
    。这意味着尖括号不会被解释为 HTML。
  • stackoverflow.com/questions/16284629/…你可以参考这个问题。
  • 由于 C++17 不需要额外的定义,参见下面的my answer

标签: c++ g++


【解决方案1】:

只是一些附加信息:

C++ 允许将整型和枚举类型的 const 静态类型“定义”为类成员。但这实际上不是一个定义,只是一个“初始化标记”

你仍然应该在课堂之外写下你的成员的定义。

9.4.2/4 - 如果一个静态数据成员是 const 整数或 const 枚举类型,它在类定义中的声明可以指定一个常量初始化器,它应该是一个整数常量表达式 (5.19)。在这种情况下,成员可以出现在整型常量表达式中。如果在程序中使用该成员,则该成员仍应在命名空间范围内定义,并且命名空间范围定义不应包含初始值设定项。

【讨论】:

    【解决方案2】:

    在 C++17 中,使用inline 变量有一个更简单的解决方案:

    struct Foo{
        inline static int member;
    };
    

    这是member 的定义,而不仅仅是它的声明。与内联函数类似,不同翻译单元中的多个相同定义不会违反 ODR。不再需要为定义选择最喜欢的 .cpp 文件。

    【讨论】:

      【解决方案3】:

      问题的出现是因为新的 C++ 功能和您正在尝试做的事情之间发生了有趣的冲突。首先我们看一下push_back的签名:

      void push_back(const T&)
      

      它期望引用T 类型的对象。在旧的初始化系统下,存在这样的成员。例如,下面的代码编译得很好:

      #include <vector>
      
      class Foo {
      public:
          static const int MEMBER;
      };
      
      const int Foo::MEMBER = 1; 
      
      int main(){
          std::vector<int> v;
          v.push_back( Foo::MEMBER );       // undefined reference to `Foo::MEMBER'
          v.push_back( (int) Foo::MEMBER ); // OK  
          return 0;
      }
      

      这是因为某处存在一个实际对象,其中存储了该值。但是,如果您切换到指定静态 const 成员的新方法,就像上面那样,Foo::MEMBER 不再是一个对象。它是一个常数,有点类似于:

      #define MEMBER 1
      

      但没有预处理器宏的麻烦(并且具有类型安全性)。这意味着需要引用的向量无法获得。

      【讨论】:

      • 谢谢,这有帮助...如果还没有stackoverflow.com/questions/1995113/strangest-language-feature,那么它可能有资格...
      • 另外值得注意的是,MSVC 毫无怨言地接受了非演员版本。
      • -1:这根本不是真的。您仍然应该定义内联初始化的静态成员,当它们在某处 odr-used 时。编译器优化可能会消除您的链接器错误并不会改变这一点。在这种情况下,您的左值到右值转换(感谢(int) 转换)发生在具有完美可见性的常量的翻译单元中,并且Foo::MEMBER 不再odr-used。这与第一个函数调用形成对比,在第一个函数调用中,引用被传递并在其他地方进行评估。
      • void push_back( const T&amp; value ); 怎么样? const&amp; 可以绑定右值。
      【解决方案4】:

      在 C++11 中,上述对于基本类型是可能的

      class Foo {
      public:  
        static constexpr int MEMBER = 1;  
      };
      

      constexpr 部分创建一个静态表达式,而不是一个静态变量——它的行为就像一个极其简单的内联方法定义。不过,在模板类中使用 C 字符串 constexprs 时,该方法被证明有点不稳定。

      【讨论】:

      • 这对我来说很重要,因为“static const int MEMBER = 1;”在开关中使用 MEMBER 是必需的,而在向量中使用它需要外部声明,并且您不能同时拥有两者。但是您在此处给出的表达式 确实 对两者都有效,至少在我的编译器中是这样。
      • @BenFarmer:这种方法不再需要 C++17 中的类外定义(因为它是隐式内联的,因为变量可以在那个版本中)。
      【解决方案5】:

      关于第二个问题:push_ref 将引用作为参数,并且不能引用类/结构的静态 const 成员。调用 static_cast 后,将创建一个临时变量。并且可以传递对该对象的引用,一切正常。

      或者至少我解决这个问题的同事是这么说的。

      【讨论】:

        【解决方案6】:

        您需要在某处实际定义静态成员(在类定义之后)。试试这个:

        class Foo { /* ... */ };
        
        const int Foo::MEMBER;
        
        int main() { /* ... */ }
        

        这应该摆脱未定义的引用。

        【讨论】:

        • 好点,内联静态常量整数初始化创建了一个范围内的整数常量,你不能获取它的地址,并且向量需要一个引用参数。
        • 此答案仅解决问题的第一部分。第二部分更有趣:为什么添加 NOP 强制转换使其无需外部声明即可工作?
        • 我刚刚花了很多时间弄清楚如果类定义在头文件中,那么静态变量的分配应该在实现文件中,而不是头文件中。
        • @shanet:非常好的观点——我应该在我的回答中提到这一点!
        • 但是如果我将它声明为const,我就不能改变那个变量的值吗?
        【解决方案7】:

        Aaa.h

        class Aaa {
        
        protected:
        
            static Aaa *defaultAaa;
        
        };
        

        Aaa.cpp

        // You must define an actual variable in your program for the static members of the classes
        
        static Aaa *Aaa::defaultAaa;
        

        【讨论】:

          【解决方案8】:

          如果需要定义静态 const 成员,C++ 标准需要定义。

          定义是必需的,例如如果使用它的地址。 push_back 通过 const 引用获取其参数,因此编译器需要您的成员的地址,您需要在命名空间中定义它。

          当您显式转换常量时,您正在创建一个临时对象,并且该临时对象与引用绑定(根据标准中的特殊规则)。

          这是一个非常有趣的案例,我实际上认为值得提出一个问题,以便将 std 更改为对您的常量成员具有相同的行为!

          虽然,以一种奇怪的方式,这可以被视为对一元“+”运算符的合法使用。基本上unary + 的结果是一个右值,因此将右值绑定到 const 引用的规则适用,我们不使用静态 const 成员的地址:

          v.push_back( +Foo::MEMBER );
          

          【讨论】:

          • +1。是的,对于 T 类型的对象 x,表达式“(T) x”可用于绑定 const ref 而普通的“x”不能,这确实很奇怪。我喜欢你对“一元+”的观察!谁会想到可怜的小“一元+”实际上有用...... :)
          • 考虑一般情况... C++ 中是否有任何其他类型的对象具有以下属性:它 (1) 只有在已定义但 (2) 时才能用作左值可以不定义就转为右值吗?
          • 好问题,至少目前我想不出任何其他例子。这可能只是在这里,因为委员会大多只是重用现有语法。
          • @RichardCorden:一元 + 如何解决这个问题?
          • @Blood-HaZaRd:在右值引用之前,push_back 的唯一重载是 const &amp;。直接使用成员会导致成员被绑定到引用,这需要它有一个地址。但是,添加 + 会创建一个具有成员值的临时值。然后引用绑定到该临时地址,而不是要求成员有地址。
          【解决方案9】:

          不知道为什么强制转换起作用,但是直到第一次加载 Foo 时才分配 Foo::MEMBER ,并且由于您从不加载它,因此永远不会分配它。如果您在某处引用了 Foo,它可能会起作用。

          【讨论】:

          • 我认为您正在回答自己的问题:演员表之所以有效,是因为它创建了(临时)参考。
          猜你喜欢
          • 2014-02-17
          • 1970-01-01
          相关资源
          最近更新 更多