静态数据成员初始化

25
为什么静态数据成员的初始化必须在类外面进行?
class X
{
public:
      int normalValue = 5; //NSDMI
      static int i;
};

int X::i = 0;

为什么静态数据成员(这里是“i”)只是声明而不是定义?


9
因为这是语言定义的方式。 ;) - jalf
你可以使用 static const int i = 10 - Charles Beattie
2
@CharlesBeattie,这仍然不是一个定义,有时需要定义,请参见http://gcc.gnu.org/wiki/VerboseDiagnostics#missing_static_const_definition。 - Jonathan Wakely
7个回答

47

区分 初始化程序(initializer)和 定义 是很重要的。下面这段经过修改后的代码是有效的,在类定义中加入了初始化程序:

class X
{
public:
  int normalValue = 5;
  static const int i = 0;       // declaration, with initializer
};

const int X::i;                 // definition
即在类外定义成员变量是定义而不是初始化,因为变量必须在内存中有地址(除非仅用于编译时常量表达式等受限情况),非静态成员变量存在于其所属对象内部,其地址取决于包含它的对象的地址。每次创建新的 X 对象时,也会创建一个新的 X::normalValue 变量。非静态数据成员的生命周期始于类的构造函数。 NSDMI 语法与变量在内存中的地址无关,它只允许您在一个地方提供初始值,而无需在每个具有显式构造函数初始化器列表的构造函数中重复。与之相反,静态成员变量不包含在类的实例中,它独立于任何单个实例而存在,并且从程序开始时就存在于固定地址。为了使静态成员变量(或任何其他全局对象)获得唯一地址,链接器必须在恰好一个对象文件中看到静态变量的恰好一个定义,并分配一个地址。因为静态变量需要恰好一个对象文件中的恰好一个定义,所以允许该定义在类中没有意义,因为类定义通常存在于头文件中并包含在多个对象文件中。因此,尽管可以在类中提供初始化程序,但仍然需要在其他地方定义静态数据成员。也可以将其视为声明 extern 变量:
namespace X {
  extern int i;
}

这声明了一个变量,但程序中必须有一个定义:

int X::i = 0;

1
如果i是一个静态数据成员,那么它不需要是constconstexpr吗? - Johannes Schaub - litb
疑问 - 每次创建新的X时,也会创建一个新的X::i变量。不应该像这样:“每次创建新的X时,也会创建一个新的X::normalValue变量”。 - Rndp13
@Rndp13,我不确定我是在建议“如果i不是静态的,则…”还是只是愚蠢,我已将其更改为normalValue,因为这更清晰,谢谢。 - Jonathan Wakely
"每次创建新的X时,也会创建一个新的X::normalValue变量。" - clickMe

7

如果一个静态数据成员(如C++11中定义的odr-used)被使用,则需要为其提供单独的定义,因为该定义必须存在于一个且仅有一个翻译单元中。静态类数据成员基本上是在类作用域中声明的全局对象(全局变量)。编译器希望您选择一个特定的翻译单元来保存每个全局对象的实际“主体”。您需要决定将实际对象放置在哪个翻译单元中。


我认为这是使用C++11进行的非静态数据成员初始化。 - CyberGuy
我从我的回答中删除了错误的“在C++11中,所有静态成员也可以有初始值设定项”的说法。 - AnT stands with Russia

3
"static" 类静态成员就像是全局分配的变量(它与单个类实例无关),因此必须在某个对象文件中存在(并在“.cpp”文件中声明为符号),就像任何全局变量一样。
简单的类成员(非静态)驻留在为类实例分配的内存块中。

2
简单的原因是因为类通常在头文件中声明,这些头文件经常被包含在多个cpp文件中。静态数据成员具有外部链接并且必须在一个翻译单元中声明,这使得它们不适合在类内定义。
正如juanchopanza所指出的那样,以下内容是允许的:
struct A
{
    const static int i = 1;
};

然而,这只是一个声明而不是定义。如果您要在某个地方使用 i 的地址,仍然需要定义它。

例如:

f(int);
g(int&);

X<A::i> x; // Okay without definition for template arguments
char a[A::i]; // Okay without definition, just using value as constant expression
&A::i; // Need a definition because I'm taking the address
f(A::i); // Okay without definition for pass by value
g(A::i); // Need a definition with pass by reference

1

当编译器从一个单元(极度简化:一个cpp文件及其所有包含的头文件)生成二进制代码时,它将为静态变量发出一个符号,并最终为该变量发出初始化代码。

静态变量符号可以在多个单元中声明,但不能多次初始化。

因此,您必须确保初始化代码仅针对单个单元发出。这意味着静态变量必须在一个单元中定义。


2
不,你混淆了初始化器和定义。初始化器可以在声明中给出,在每个TU中都会重复。定义(带或不带初始化器)只能存在于一个地方。 - Jonathan Wakely
与其考虑初始化,我发现按地址思考更有用。静态变量必须有一个地址,而且只能有一个地址,因此它必须在一个翻译单元中定义,以便链接器可以为其分配一个地址。 - Jonathan Wakely

1
请注意,如果静态数据成员是const整数类型或const枚举类型,则可以在声明时初始化:
来自C++03标准,§9.4.2

如果静态数据成员是const整数或const枚举类型,则其在类定义中的声明可以指定一个常量初始化器,该初始化器应为整数常量表达式(5.19)

struct Foo {
  static const int j = 42; // OK
};

2
但即使如此,如果变量被odr-used使用,仍然需要定义。 - Jonathan Wakely

0

静态数据成员

#include<iostream.h>
#include<conio.h>

class static_var
{

static int count; //static member of class
public :

void incr_staticvar()
{
count++;
}

void outputc()
{ 
cout<<"Value of Static variable Count :- "<<count<<endl;
}
};

int static_var::count;

void main()
{
clrscr();
static_var obj1,obj2,obj3,obj4;

obj1.incr_staticvar();
obj2.incr_staticvar();
obj3.incr_staticvar();
obj4.incr_staticvar();

cout<<"\nAfter Increment of static variable by Four Different objects is :-\n";

obj1.outputc ( );
obj2.outputc ( );
obj3.outputc ( );
obj4.outputc ( );

getch();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接