我正在使用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
我正在努力寻找官方参考资料,但我从Gregoire,Solter& Co.的第二版“专业C ++”一书中获取了这段代码。 Kleper(第7章,第181页,“静态数据成员”小节),它应该可以工作。
使用C ++ 11,这就是你需要做的。如果你正在使用C ++ pior到C ++ 11,它有点笨拙[...]
然后它建议我采用传统方式。
作者是错的吗?
答案 0 :(得分:3)
再次阅读错误消息:
ISO C ++禁止非const 静态成员'Spreadsheet :: sCounter'的类内初始化 static int sCounter = 0;
如果要在类定义中初始化static
成员,则需要const
,这就是它的全部内容。
在构造该类的对象时初始化普通类成员。您可以将类内初始值设定项视为成员初始值设定项列表的语法糖(或者将它们视为与默认函数参数类似)。您仍然可以覆盖它并使用旧方法(在构造函数中)使用不同的表达式初始化它们。
静态成员不同。它们不属于任何类实例,因此需要为它们分配内存并单独初始化。因为它们具有静态存储持续时间,所以它位于程序启动的某个地方。
常量静态成员是一个例外。由于无法写入,标准允许 你在类声明中初始化它们,它们基本上被视为一个值,而不是一个对象。也就是说,直到你对它们做某事需要将它们存储在某个地方,例如拿走它们的地址。这在C ++ 11中没有改变。
标准引用,来自n3337草案,第9.4.2章(强调我的):
2在类定义中声明静态数据成员不是一个定义,可能是不完整的 除了cv-quali fi ed void之外的类型。 静态数据成员的定义应出现在命名空间中 应对成员的类定义。在命名空间作用域的定义中,静态的名称 数据成员应使用::运算符通过其类名限定。初始化表达式 静态数据成员的定义属于其类(3.3.7)的范围。 [...]
和
3 如果非易失性const静态数据成员是整数或枚举类型,则其在类中声明 定义可以指定一个括号或等于初始化器,其中每个初始化子句都是一个赋值 表达式是一个常量表达式(5.19)。可以在。中声明文字类型的静态数据成员 使用constexpr指定程序进行分类;如果是这样,其声明应指定一个支撑或等于初始化器 其中作为赋值表达式的每个initializer子句都是一个常量表达式。 [注意:两者都有 在这些情况下,成员可能出现在常量表达式中。 -end note] 该成员仍然应该被定义 在命名空间范围内,如果程序中使用了odr-used(3.2),则命名空间范围定义不得 包含初始化程序。
答案 1 :(得分:1)
将静态定义移出类声明:
class Spreadsheet
{
...
static int sCounter;
};
int Spreadsheet::sCounter = 0;
答案 2 :(得分:1)
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
};
BTW,静态const和非静态类数据成员的类内初始化器的语义和基本原理完全不同。尽管在语法上是相似的,但这些实际上是两个不同的特征,而不是前者对后者的扩展。
存在用于静态const数据成员的类内初始化器,以促进“早期”积分常量表达式,即它们在类定义点将静态常量转换为ICE(与静态成员的后续点相对)定义)。
非静态数据成员的类内初始化程序有助于构造函数中类成员的隐式初始化。换句话说,C ++ 11中的内容看起来像非静态数据成员的类初始化器并没有真正初始化任何东西。 (此时没有任何内容可以初始化。)所有这些类内初始化程序都会为编译器提供稍后用于生成类构造函数的信息。当您真正开始定义该类类型的对象时,这些构造函数将在以后工作。