【问题标题】:Why can't we have non-const class-level static variables?为什么我们不能有非常量类级别的静态变量?
【发布时间】:2011-10-17 16:50:28
【问题描述】:

为什么the Visual C++ compiler refuse to compile this code

我显然知道错误是:

错误 C2864:Singleton<T>::p:
只能在类中初始化静态 const 整数数据成员

但是为什么? (即,是否有技术原因不允许这样做?)
这是特定于编译器的行为还是标准规定的?
在全局范围内似乎很好,那么为什么不在类范围内呢?

也好像是not all compilers mind this

另外,解决这个问题的正确方法是什么?

template<typename T>
struct Singleton
{
    static T *p = 0;  // Error C2864

    static T *getInstance() { /*...*/ return p; }
};

【问题讨论】:

  • 这个特殊规则被引入到语言中的唯一目的是让愚蠢的 Singleton 类更难编写。如果你认为你需要一个 Singleton,那么 99% 的情况你都错了。
  • @FredOverflow:等一下,说真的?!那么,当您知道静态函数的输出在程序的生命周期内不能更改时,如何缓存它?
  • @Mehrdad:可能是函数范围的静态变量。或者在 C++11 中,也许你可以在编译时使用constexpr 来计算它。这和单例有什么关系?
  • @Mehrdad 不要告诉我你正在上 cached_output_of_f_singleton 课程。对于如此简单的事情,这听起来需要做很多工作。
  • ideone 上的示例可以编译,因为您从不实例化模板。如果这样做,或者将 Singleton 设为非模板类​​,您将得到同样的错误。

标签: c++ visual-c++ singleton static-members


【解决方案1】:

这是标准行为。只有静态 const 整数成员可以在没有正确定义的情况下进行初始化。所有其他类型都需要在某处定义,初始化写在定义点:

template<typename T>
struct Singleton {
    static T *p;

    static T *getInstance() { /*...*/ return p; }
};

template<typename T>
T *Singleton<T>::p = 0;

对象必须在某处定义。如果你在你的类中定义它们,那么它在一个头文件中定义,你会为每个包含它的编译单元获得一个不同的对象。这对于 const 整数类型来说是放松的,如果你没有定义它们,那么编译器只会用它的文字值替换它。如果未提供定义,获取此类静态 const 积分的地址仍会导致链接器错误。

【讨论】:

  • 知道为什么这是不允许的吗?
  • @Mehrdad:因为对象必须在某处定义。如果你在你的类中定义它们,那么它在一个头文件中定义,你会为每个包含它的编译单元获得一个不同的对象。这对于 const 整数类型来说是宽松的,如果你没有定义它们,那么编译器只会用它的字面值替换它,但是获取这种静态的地址是未定义的行为。
  • 嗯,我从来没有意识到头文件的问题,很好。 +1
  • 评论和对答案的补充在两种不同的方面是错误的。第一个是在类定义中你从不define成员变量,而只是declare。您不能永远在此处定义静态成员。错误的第二部分是第一部分的结果:尝试获取在类定义中提供值的整型常量静态成员的地址并没有错。这是对静态成员的完全有效的使用并且是允许的。
  • @K-ballo:不,只要您在一个翻译单元中定义变量就可以了。为了帮助您找到引用,它位于第 3 节中围绕 One 定义规则 的某处,同样,它是 not 未定义的行为——该行为的定义非常明确:实现将拒绝代码并且无法链接。因为变量是never定义的,它没有any地址,所以不会,它不会产生不同的地址。
【解决方案2】:

你可以拥有这种类型的变量,但你不能在类定义中初始化它。唯一可以按照您要求的方式初始化的变量类型是 static const

您的类定义的固定版本删除了= 0,并将其添加到类定义下方:

template<typename T>
T *Singleton<T>::p = 0;

这是标准行为。我不确定是否有 技术 原因,我的猜测是为了与实例成员保持一致(也不能以这种方式初始化)。当然,实例变量有构造函数初始化列表。

【讨论】:

    【解决方案3】:

    正如每个人都指出的那样,您不能在类的主体中定义非常量、非整数类型(至少在 C++03 中没有,它在 C++11 中发生了变化,但我不确定如何确切地)。但是,您可以采用不同的方式进行清洁。

    template<typename T>
    struct Singleton {
        static T* getInstance() {
            static T* p = NULL;
            /*...*/
            return p;
        }
    };
    

    【讨论】:

      【解决方案4】:

      声明用于进入头文件,它们将被编译多次 - 每个位置都包含它们。

      静态变量应该只有一个定义,这样整个程序中只会存在一个副本。这意味着它需要位于源 (.cpp) 文件中。分配值需要放在那个位置。

      静态常量整数是上述规则的一个例外,因为它们可以成为编译时常量。使用它们时,会用文字值替换类成员。

      【讨论】:

        【解决方案5】:

        你不能像那样在类体中分配一个非静态的。相反,在类之外分配值(通常在您的 cpp 文件中)

        template<typename T>
        struct Singleton {
            static T *p;
        
            static T *getInstance() { /*...*/ return p; }
        };
        
        template<typename T>
        T *Singleton<T>::p = 0;
        

        【讨论】:

          【解决方案6】:

          有趣的问题不是你问的问题,而是相反的问题:

          为什么允许在声明中为常量整型静态成员赋值?

          问题的重要一点是声明。变量获得的值在变量定义中设置,这与类的非常量或非整数静态成员一致,在类中您只提供声明。初始化器值在类定义之外的定义中提供,通常在保证它将在单个翻译单元中定义的 .cpp 中。

          但是为什么整型静态常量在声明中可以有值呢?

          出于实际原因。编译器可以使用整型常量作为compile-time常量,也就是说,它实际上可以在常量所在的所有地方用常量的value代替标识符用作右值,例如在定义数组大小时。但是如果 value 只出现在单个翻译单元的定义中,编译器就不可能在所有其他翻译单元中使用它。举个例子:

          // fq.h
          struct fixed_queue {
             static const std::size_t max_elements; // [1]
             int data[ max_elements ];              // Error: How big is data??
          };
          // fq.cpp
          #include "fq.h"
          const std::size_t fixed_queue::max_elements = 10;
          

          如果 max_elements 不允许在声明 [1] 中有值,那么您将无法使用该常量来定义数组 data 的大小,这是一个非常明智的使用静态常量。

          为什么不将此扩展到所有其他情况?

          因为它似乎没有多大意义......在类定义中提供值不能被编译器在任何其他情况下使用,因此不需要它,所以只需要区别对待整数常量。

          【讨论】:

          • "为什么不将其扩展到所有其他情况?" Java 确实将其扩展到其他情况。有人可以澄清为什么会这样吗?
          【解决方案7】:

          原因是非整数、非常量值需要一个内存位置。

          const int 可以由编译器静态处理并直接构建到某些机器指令中,浮点数和更多奇特的对象需要在某个地方存在,因为机器指令只会根据它们的地址进行操作。

          原则上,该语言本可以允许这样做,但这意味着要么生成额外的对象并使二进制文件膨胀(对于 const 来说还可以),要么让编译器编写者为非 const 编写者感到困难:冗余副本必须是删除以保留单一定义规则(顺便说一句,这是模板实例化必须做的)。

          【讨论】:

          • 全局变量不也是这样吗?
          • 全局变量必须是extern,如果不是const int,直接类推。
          • @Mehrdad:是的,除了任何声明之外,它们还需要一个单一的定义。
          • 问题是像这样的类定义中声明静态数据成员不是定义,所以它不能有初始化器。
          猜你喜欢
          • 1970-01-01
          • 2011-12-08
          • 1970-01-01
          • 2011-01-14
          • 2016-08-01
          • 2012-10-22
          • 1970-01-01
          • 2012-03-28
          相关资源
          最近更新 更多