C++ C++;静态成员变量及其初始化

C++ C++;静态成员变量及其初始化,c++,initialization,static-variables,C++,Initialization,Static Variables,对于C++类中的静态成员变量,初始化是在类之外完成的。我想知道为什么?对此有何逻辑推理/限制?还是纯粹的遗留实现——标准不想纠正 我认为在类中进行初始化更“直观”,也更容易混淆。它还提供了变量的静态性和全局性。例如,如果您看到static const成员。这是因为代码的编译方式。如果要在类中初始化它(通常在标题中),那么每次包含标题时,都会得到静态变量的实例。这绝对不是目的。在类之外初始化它可以在cpp文件中初始化它。我认为在类块之外进行初始化的主要原因是允许使用其他类成员函数的返回值进行初始化


对于C++类中的静态成员变量,初始化是在类之外完成的。我想知道为什么?对此有何逻辑推理/限制?还是纯粹的遗留实现——标准不想纠正


我认为在类中进行初始化更“直观”,也更容易混淆。它还提供了变量的静态性和全局性。例如,如果您看到static const成员。

这是因为代码的编译方式。如果要在类中初始化它(通常在标题中),那么每次包含标题时,都会得到静态变量的实例。这绝对不是目的。在类之外初始化它可以在cpp文件中初始化它。

我认为在
块之外进行初始化的主要原因是允许使用其他类成员函数的返回值进行初始化。如果您想使用
b::some_static\u fn()
初始化
a::var
,您需要确保包含
a.h
的每个
.cpp
文件首先包含
b.h
。这将是一个混乱,尤其是当(早晚)您遇到一个循环引用时,您只能使用一个不必要的
接口来解析它。同样的问题也是将类成员函数实现放在
.cpp
文件中而不是将所有内容都放在主类“
.h
中的主要原因


至少对于成员函数,您可以选择在标题中实现它们。对于变量,必须在.cpp文件中进行初始化。我不太同意这个限制,我也不认为有什么好的理由。基本上,这是因为静态成员必须在一个翻译单元中定义,才能不违反规则。如果语言允许以下内容:

struct Gizmo
{
  static string name = "Foo";
};
然后在包含此头文件的每个翻译单元中定义
名称

C++确实允许您在声明中定义整型静态成员,但您仍然必须在单个翻译单元中包含一个定义,但这只是一种快捷方式或语法糖。因此,这是允许的:

struct Gizmo
{
  static const int count = 42;
};
只要a)表达式是
const
integral或枚举类型,b)表达式可以在编译时求值,c)仍然有一个不违反一个定义规则的定义:

文件:gizmo.cpp

#include "gizmo.h"

const int Gizmo::count;

<>在C++中,初始化器的存在是对象定义的唯一属性,即用初始化器声明总是一个定义(几乎总是)。p>

正如你必须知道的,C++程序中的每个外部对象必须在一个翻译单元中只定义一次。允许静态对象使用类内初始值设定项将立即违反此约定:初始值设定项将进入头文件(类定义通常驻留的位置),从而生成同一静态对象的多个定义(包括头文件的每个翻译单元一个)。这当然是不可接受的。因此,静态类成员的声明方法完全是“传统的”:只在头文件中声明(即不允许使用初始值设定项),然后在您选择的翻译单元中定义它(可能使用初始值设定项)


此规则的一个例外是针对整型或枚举类型的const static类成员,因为这样的条目可以用于整型常量表达式(ICE)。ICEs的主要思想是在编译时对其进行评估,因此不依赖于所涉及对象的定义。这就是为什么整型或枚举类型可能出现此异常。但是对于其他类型,它将与C++的基本声明/定义原则相矛盾。

< P>节94.2,静态数据成员,C++标准状态:

如果
静态
数据成员属于
常量
整数或
常量
枚举类型,则其在类定义中的声明可以指定常量初始值设定项,该初始值设定项应为整数常量表达式

因此,静态数据成员的值可能包含在“类内”(我假定您的意思是在类的声明内)。但是,静态数据成员的类型必须是
const
整数或
const
枚举类型。不能在类声明中指定其他类型的静态数据成员的值的原因是可能需要进行非平凡的初始化(即,需要运行构造函数)

想象一下,如果以下是合法的:

// my_class.hpp
#include <string>

class my_class
{
public:
  static std::string str = "static std::string";
//...
编辑查看g++的汇编程序输出以获取以下代码是很有意义的:

// SO4547660.cpp
#include <string>

class my_class
{
public:
    static std::string str;
};

std::string my_class::str = "static std::string";
通过查看g++生成的
SO4547660.s
文件,您可以看到对于这样一个小的源文件有很多代码

\uuuuzn8my\u class3strE
my\u class::str的存储空间标签。还有一个
\uuuuuu static\u initialization\u和\u destruction\u 0(int,int)
函数的汇编源代码,它的标签是
\uuuuuuz41\uuuu static\u initialization\u和\u destruction\u 0ii
。该函数对g++来说是特殊的,但要知道g++将确保在执行任何非初始值设定项代码之前调用它。请注意,此函数的实现调用了
\uznscsc1epkcrksaice
。这是
std::basic_string::basic_string(char const*,std::allocator const&)
的损坏符号

回到上面的假设示例,使用这些细节,与包含
my_class.hpp
的CPP文件相对应的每个对象文件都会有标签
\uuuzn8my\uclass3stre
用于
si
// SO4547660.cpp
#include <string>

class my_class
{
public:
    static std::string str;
};

std::string my_class::str = "static std::string";
g++ -S SO4547660.cpp