C++静态成员变量及其初始化
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4547660/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
C++ static member variable and its initialization
提问by kumar_m_kiran
For static member variables in C++ class - the initialization is done outside the class. I wonder why? Any logical reasoning/constraint for this? Or is it purely legacy implementation - which the standard does not want to correct?
对于 C++ 类中的静态成员变量 - 初始化在类之外完成。我想知道为什么?对此有何逻辑推理/约束?还是纯粹的遗留实现——标准不想纠正?
I think having initialization in the class is more "intuitive" and less confusing.It also gives the sense of both static and global-ness of the variable. For example if you see the static const member.
我认为在类中进行初始化更“直观”并且不那么混乱。它还提供了变量的静态和全局性的意义。例如,如果您看到静态常量成员。
采纳答案by John Dibling
Fundamentally this is because static members must be defined in exactly one translation unit, in order to not violate the One-Definition Rule. If the language were to allow something like:
从根本上说,这是因为必须在一个翻译单元中定义静态成员,以免违反单一定义规则。如果语言允许以下内容:
struct Gizmo
{
static string name = "Foo";
};
then name
would be defined in each translation unit that #include
s this header file.
然后name
将在#include
这个头文件的每个翻译单元中定义。
C++ does allow you to define integralstatic members within the declaration, but you still have to include a definition within a single translation unit, but this is just a shortcut, or syntactic sugar. So, this is allowed:
C++ 确实允许您在声明中定义完整的静态成员,但您仍然必须在单个翻译单元中包含一个定义,但这只是一种快捷方式或语法糖。所以,这是允许的:
struct Gizmo
{
static const int count = 42;
};
So long as a) the expression is const
integral or enumeration type, b) the expression can be evaluated at compile-time, and c) there is still a definition somewhere that doesn't violate the one definition rule:
只要 a) 表达式是const
整数或枚举类型,b) 表达式可以在编译时求值,并且 c) 在某处仍然有一个不违反一个定义规则的定义:
file: gizmo.cpp
文件:gizmo.cpp
#include "gizmo.h"
const int Gizmo::count;
回答by AnT
In C++ since the beginning of times the presence of an initializerwas an exclusive attribute of object definition, i.e. a declaration with an initializer is always a definition(almost always).
在 C++ 中,从一开始,初始化器的存在就是对象定义的专有属性,即带有初始化器的声明总是一个定义(几乎总是)。
As you must know, each external object used in C++ program has to be defined once and only once in only one translation unit. Allowing in-class initializers for static objects would immediately go against this convention: the initializers would go into header files (where class definitions usually reside) and thus generate multiple definitions of the same static object (one for each translation unit that includes the header file). This is, of course, unacceptable. For this reason, the declaration approach for static class members is left perfectly "traditional": you only declareit in the header file (i.e. no initializer allowed), and then you defineit in a translation unit of your choice (possibly with an initializer).
您必须知道,C++ 程序中使用的每个外部对象都必须定义一次,并且仅在一个翻译单元中定义一次。允许静态对象的类内初始值设定项将立即违反此约定:初始值设定项将进入头文件(类定义通常驻留的位置)并因此生成同一静态对象的多个定义(每个翻译单元包含头文件一个) )。这当然是不可接受的。出于这个原因,静态类成员的声明方法完全是“传统的”:您只在头文件中声明它(即不允许初始化器),然后在您选择的翻译单元中定义它(可能使用初始化器)。
One exception from this rule was made for const static class members of integral or enum types, because such entries can for Integral Constant Expressions (ICEs). The main idea of ICEs is that they are evaluated at compile time and thus do no depend on definitions of the objects involved. Which is why this exception was possible for integral or enum types. But for other types it would just contradict the basic declaration/definition principles of C++.
此规则的一个例外是针对整型或枚举类型的 const 静态类成员,因为此类条目可用于整型常量表达式 (ICE)。ICE 的主要思想是它们在编译时进行评估,因此不依赖于所涉及对象的定义。这就是整数或枚举类型可能出现此异常的原因。但是对于其他类型,它只会与 C++ 的基本声明/定义原则相矛盾。
回答by RedX
It's because of the way the code is compiled. If you were to initialize it in the class, which often is in the header, every time the header is included you'd get an instance of the static variable. This is definitely not the intent. Having it initialized outside the class gives you the possibility to initialize it in the cpp file.
这是因为代码的编译方式。如果您要在类中初始化它(通常在标题中),则每次包含标题时,您都会获得静态变量的实例。这绝对不是本意。在类外初始化它使您可以在 cpp 文件中初始化它。
回答by Daniel Trebbien
Section 9.4.2, Static data members, of the C++ standard states:
C++ 标准的第 9.4.2 节,静态数据成员指出:
If a
static
data member is ofconst
integral orconst
enumeration type, its declaration in the class definition can specify a const-initializerwhich shall be an integral constant expression.
如果一个
static
数据成员是const
整型或const
枚举类型,它在类定义中的声明可以指定一个常量初始化器,它应该是一个整型常量表达式。
Therefore, it is possible for the value of a static data member to be included "within the class" (by which I presume that you mean within the declaration of the class). However, the type of the static data member must be a const
integral or const
enumeration type. The reason why the values of static data members of other types cannot be specified within the class declaration is that non-trivial initialization is likely required (that is, a constructor needs to run).
因此,静态数据成员的值可能包含在“类中”(我认为您的意思是在类的声明中)。但是,静态数据成员的类型必须是const
整数或const
枚举类型。不能在类声明中指定其他类型的静态数据成员的值的原因是可能需要非平凡的初始化(即需要运行构造函数)。
Imagine if the following were legal:
想象一下,如果以下是合法的:
// my_class.hpp
#include <string>
class my_class
{
public:
static std::string str = "static std::string";
//...
Each object file corresponding to CPP files that include this header would not only have a copy of the storage space for my_class::str
(consisting of sizeof(std::string)
bytes), but also a "ctor section" that calls the std::string
constructor taking a C-string. Each copy of the storage space for my_class::str
would be identified by a common label, so a linker could theoretically merge all copies of the storage space into a single one. However, a linker would not be able to isolate all copies of the constructor code within the object files' ctor sections. It would be like asking the linker to remove all of the code to initialize str
in the compilation of the following:
对应于包含此标头的 CPP 文件的每个目标文件不仅具有my_class::str
(由sizeof(std::string)
字节组成)的存储空间的副本,而且还有一个“std::string
构造函数部分”,它调用带有 C 字符串的构造函数。存储空间的每个副本my_class::str
都由一个公共标签标识,因此链接器理论上可以将存储空间的所有副本合并为一个。但是,链接器将无法隔离目标文件的 ctor 部分中构造函数代码的所有副本。这就像要求链接器删除所有代码以str
在以下编译中初始化:
std::map<std::string, std::string> map;
std::vector<int> vec;
std::string str = "test";
int c = 99;
my_class mc;
std::string str2 = "test2";
EDITIt is instructive to look at the assembler output of g++ for the following code:
编辑查看以下代码的 g++ 汇编器输出是有益的:
// SO4547660.cpp
#include <string>
class my_class
{
public:
static std::string str;
};
std::string my_class::str = "static std::string";
The assembly code can be obtained by executing:
可以通过执行获得汇编代码:
g++ -S SO4547660.cpp
Looking through the SO4547660.s
file that g++ generates, you can see that there is a lot of code for such a small source file.
翻看SO4547660.s
g++生成的文件,可以看到这么小的源文件,代码很多。
__ZN8my_class3strE
is the label of the storage space for my_class::str
. There is also the assembly source of a __static_initialization_and_destruction_0(int, int)
function, which has the label __Z41__static_initialization_and_destruction_0ii
. That function is special to g++ but just know that g++ will make sure that it gets called before any non-initializer code gets executed. Notice that the implementation of this function calls __ZNSsC1EPKcRKSaIcE
. This is the mangled symbol for std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)
.
__ZN8my_class3strE
是 的存储空间标签my_class::str
。还有一个__static_initialization_and_destruction_0(int, int)
函数的汇编源代码,它的标签是__Z41__static_initialization_and_destruction_0ii
。该函数对于 g++ 是特殊的,但只要知道 g++ 将确保在执行任何非初始化程序代码之前调用它。请注意,此函数的实现调用__ZNSsC1EPKcRKSaIcE
. 这是 的损坏符号std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)
。
Going back to the hypothetical example above and using these details, each object file corresponding to a CPP file that includes my_class.hpp
would have the label
__ZN8my_class3strE
for sizeof(std::string)
bytes as well as assembly code to call __ZNSsC1EPKcRKSaIcE
within its implementation of the __static_initialization_and_destruction_0(int, int)
function. The linker can easily merge all occurrences of __ZN8my_class3strE
, but it cannot possibly isolate the code that calls __ZNSsC1EPKcRKSaIcE
within the object file's implementation of __static_initialization_and_destruction_0(int, int)
.
让我们再回到上面的假设的例子,并使用这些细节,对应到包括CPP文件中的每个目标文件my_class.hpp
将具有标签
__ZN8my_class3strE
为sizeof(std::string)
字节以及汇编代码呼叫__ZNSsC1EPKcRKSaIcE
其实施的内__static_initialization_and_destruction_0(int, int)
功能。链接器可以轻松地合并所有出现的__ZN8my_class3strE
,但它不可能隔离__ZNSsC1EPKcRKSaIcE
在目标文件的 实现中调用的代码__static_initialization_and_destruction_0(int, int)
。
回答by martona
I think the main reason to have initialization done outside the class
block is to allow for initialization with return values of other class member functions. If you wanted to intialize a::var
with b::some_static_fn()
you'd need to make sure that every .cpp
file that includes a.h
includes b.h
first. It'd be a mess, especially when (sooner or later) you run into a circular reference that you could only resolve with an otherwise unnecessary interface
. The same issue is the main reason for having class member function implementations in a .cpp
file instead of putting everything in your main class' .h
.
我认为在class
块外进行初始化的主要原因是允许使用其他类成员函数的返回值进行初始化。如果您想初始化a::var
,b::some_static_fn()
您需要确保包含的每个.cpp
文件都首先包含a.h
在内b.h
。这将是一团糟,尤其是当(迟早)您遇到循环引用时,您只能使用其他不必要的interface
. 同样的问题是将类成员函数实现放在.cpp
文件中而不是将所有内容都放在主类中的主要原因.h
。
At least with member functions you do have the option to implement them in the header. With variables you must do the initialization in a .cpp file. I don't quite agree with the limitation, and I don't think there's a good reason for it either.
至少对于成员函数,您可以选择在标头中实现它们。对于变量,您必须在 .cpp 文件中进行初始化。我不太同意这种限制,而且我认为也没有充分的理由。