即使启用了 C++11,静态成员初始化也不适用于 GCC

Static member initialization not working with GCC even with C++11 enabled

本文关键字:初始化 不适用 适用于 GCC 静态成员 启用 C++11      更新时间:2023-10-16

我正在使用GCC 4.8.2:

$ g++ --version                          
g++ (GCC) 4.8.2
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

我有这个类定义:

#pragma once
#include "SpreadsheetCell.h"
class Spreadsheet 
{
    public:
        Spreadsheet(int inWidth, int inHeight);
        Spreadsheet(const Spreadsheet& src);
        ~Spreadsheet();
        int getId() const;
        int getWidth() const;
        int getHeight() const;
        void setCellAt(int x, int y, const SpreadsheetCell& cell);
        SpreadsheetCell getCellAt(int x, int y) const;
        Spreadsheet& operator=(const Spreadsheet& rhs);
    private:
        bool inRange(int val, int upper) const;
        void copyFrom(const Spreadsheet& src);
        void freeMemory();
        int mWidth, mHeight, mId;
        SpreadsheetCell** mCells;
        static int sCounter = 0;
};

当我尝试编译它时,我得到:

$ make SpreadsheetTest && SpreadsheetTest
g++ -Wall -g -std=c++11 -c Spreadsheet.cpp
In file included from Spreadsheet.cpp:3:0:
Spreadsheet.h:27:31: error: ISO C++ forbids in-class initialization of non-const static member ‘Spreadsheet::sCounter’
         static int sCounter = 0;
                               ^
Makefile:11: recipe for target 'Spreadsheet.o' failed
make: *** [Spreadsheet.o] Error 1

奇怪的是,如果我从声明中删除static修饰符sCounter它会正常编译。

这是怎么回事?

编辑:

似乎此功能从 GCC 4.7 开始可用:http://gcc.gnu.org/projects/cxx0x.html

编辑 2:

我试图找到官方参考,但我从Gregoire,Solter和Kleper(第7章,第181页,"静态数据成员"小节)的"专业C++"第2版"一书中获取了这段代码,它应该可以工作。

有了 C++11,这就是您需要做的。如果您使用 C++ pior 来 C++11,它会有点笨拙 [...]

然后它建议我做传统的方式。

作者错了吗?

再次阅读错误消息:

ISO C++禁止在类内初始化非常量静态成员"Spreadsheet::sCounter" 静态 int sCounter = 0;

如果要在类定义中初始化static成员,则需要对其进行const,仅此而已。

普通类成员在构造该类的对象时初始化。可以将类内初始值设定项视为成员初始值设定项列表的语法糖(或将它们视为类似于默认函数参数)。您仍然可以覆盖它并使用旧方式(在构造函数中)使用不同的表达式初始化它们。

静态成员是不同的。它们不属于任何类实例,因此需要为它们分配内存并单独初始化。由于它们具有静态存储持续时间,因此在程序启动时的某个地方。

常量静态成员是一个例外。由于无法写入,因此标准允许你在类声明中初始化它们,它们基本上被视为一个值,而不是一个对象。也就是说,直到您对它们执行需要将它们存储在某个地方的操作,例如获取它们的地址。这在 C++11 中没有改变。

标准引文,来自 n3337 草案,第 9.4.2 章(强调我的):

2 静态数据成员在其类定义中的声明不是定义,并且可能不完整 除符合 CV 标准的空隙外,其他类型。静态数据成员的定义应出现在命名空间中 将成员的类定义括起来。在命名空间范围的定义中,静态的名称 数据成员应使用::运算符按其类名限定。初始值设定项表达式在 静态数据成员的定义在其类 (3.3.7) 的范围内。[...]

3 如果非易失性 const 静态数据成员是整型或枚举型,则其在类中的声明 定义可以指定大括号或等于初始值设定项,其中每个初始值设定项子句都是赋值 表达式是一个常量表达式 (5.19)。文本类型的静态数据成员可以在 使用 constexpr 说明符进行类定义;如果是这样,其声明应指定大括号或等于初始值设定项 其中作为赋值表达式的每个初始值设定项子句都是一个常量表达式。[注:在两者中 在这些情况下,成员可能出现在常量表达式中。—尾注 ] 成员仍应定义 在命名空间作用域中,如果它在程序中是 ODR 使用的 (3.2) 并且命名空间作用域定义不应 包含初始值设定项。

将静态定义移出类声明:

class Spreadsheet 
{
   ...
   static int sCounter;
};
int Spreadsheet::sCounter = 0;
  • C++98 允许对整型或枚举类型的静态const成员使用类内初始值设定项。

  • C++11 还允许非静态成员的类内初始值设定项。

您的代码不属于任一类别。它不应该在 C++98 中编译,也不应该在 C++11 中编译。在C++不允许为非 const 静态成员提供类内初始值设定项。

另请注意,原始 C++98 功能尚未扩展到非整数非枚举类型

struct S {
  static const double d = 5; // still an error even in C++11
};

顺便说一句,静态常量和非静态类数据成员的类内初始值设定项的语义和基本原理是完全不同的。尽管在语法上相似,但这实际上是两个不同的特征,而不是前者对后者的延伸。

    静态常量
  • 数据成员的类内初始值设定项的存在是为了促进"早期"整型常量表达式,即它们在类定义点(而不是静态成员定义的后期点)将静态常量转换为 ICE。

  • 非静态数据成员的类内初始值设定项有助于在构造函数中隐式初始化类成员。换句话说,C++11 中看起来像非静态数据成员的类内初始值设定项尚未真正初始化任何内容。(此时没有要初始化的内容。所有这些类内初始值设定项所做的只是向编译器提供稍后用于生成类构造函数的信息。这些构造函数甚至在以后实际开始定义该类类型的对象时也会完成它们的工作。