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

发布于 2024-10-09 03:28:38 字数 161 浏览 4 评论 0 原文


对于 C++ 类中的静态成员变量 - 初始化是在类外部完成的。我想知道为什么?对此有何逻辑推理/约束?或者它纯粹是遗留实现——​​标准不想纠正它?

我认为在类中进行初始化更“直观”并且更不易混淆。它还给出了变量的静态和全局性的感觉。例如,如果您看到 static const 成员。

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?

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

最佳男配角 2024-10-16 03:28:38

从根本上来说,这是因为静态成员必须在一个翻译单元中定义,以免违反 单一定义规则。如果语言允许类似以下内容:

struct Gizmo
{
  static string name = "Foo";
};

那么 name 将在 #include 作为此头文件的每个翻译单元中定义。

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

struct Gizmo
{
  static const int count = 42;
};

只要 a) 表达式是 const 整型或枚举类型,b) 表达式可以在编译时求值,并且 c) 仍然有一个定义不存在不要违反单一定义规则:

文件:gizmo.cpp

#include "gizmo.h"

const int Gizmo::count;

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 #includes this header file.

C++ does allow you to define integral static 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:

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:

file: gizmo.cpp

#include "gizmo.h"

const int Gizmo::count;
东走西顾 2024-10-16 03:28:38

在 C++ 中,自古以来,初始化器的存在就是对象定义的独占属性,即带有初始化器的声明始终是定义 >(几乎总是)。

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

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

In C++ since the beginning of times the presence of an initializer was an exclusive attribute of object definition, i.e. a declaration with an initializer is always a definition (almost always).

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 declare it in the header file (i.e. no initializer allowed), and then you define it in a translation unit of your choice (possibly with an initializer).

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++.

征﹌骨岁月お 2024-10-16 03:28:38

这是因为代码的编译方式。如果您要在类中初始化它(通常位于标头中),则每次包含标头时,您都会获得静态变量的实例。这绝对不是本意。在类外部对其进行初始化使您可以在 cpp 文件中对其进行初始化。

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.

一桥轻雨一伞开 2024-10-16 03:28:38

C++ 标准的第 9.4.2 节“静态数据成员”指出:

如果static数据成员是const整型或const枚举类型,则其在类定义中的声明可以指定 const-initializer 应该是一个整型常量表达式。

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

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

// my_class.hpp
#include <string>

class my_class
{
public:
  static std::string str = "static std::string";
//...

与包含此标头的 CPP 文件对应的每个对象文件不仅具有 my_class::str 存储空间的副本(由 sizeof(std: :string) 字节),但也是一个“ctor 部分”,它调用采用 C 字符串的 std::string 构造函数。 my_class::str 存储空间的每个副本都将由一个公共标签来标识,因此链接器理论上可以将存储空间的所有副本合并为一个副本。但是,链接器无法隔离对象文件的构造函数部分中构造函数代码的所有副本。这就像要求链接器删除以下编译中初始化 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";

编辑 查看 g++ 的汇编器输出是有启发性的如下代码:

// SO4547660.cpp
#include <string>

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

std::string my_class::str = "static std::string";

可以通过执行得到汇编代码:

g++ -S SO4547660.cpp

查看g++生成的SO4547660.s文件,可以看到这么小的源文件有很多代码。

__ZN8my_class3strEmy_class::str存储空间的标签。还有 __static_initialization_and_destruction_0(int, int) 函数的汇编源代码,其标签为 __Z41__static_initialization_and_destruction_0ii。该函数对于 g++ 来说是特殊的,但只要知道 g++ 将确保在执行任何非初始化程序代码之前调用它即可。请注意,此函数的实现调用__ZNSsC1EPKcRKSaIcE。这是 std::basic_string、std::allocator 的损坏符号。 >::basic_string(char const*, std::allocator const&)。

回到上面的假设示例并使用这些详细信息,与包含 my_class.hpp 的 CPP 文件对应的每个目标文件都将具有标签
__ZN8my_class3strE 用于 sizeof(std::string) 字节以及在其实现 __static_initialization_and_destruction_0(int , int) 函数。链接器可以轻松合并所有出现的 __ZN8my_class3strE,但它不可能在 __static_initialization_and_destruction_0(int, int)< 的目标文件实现中隔离调用 __ZNSsC1EPKcRKSaIcE 的代码。 /代码>。

Section 9.4.2, Static data members, of the C++ standard states:

If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a const-initializer which shall be an integral constant expression.

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).

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:

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";

EDIT It is instructive to look at the assembler output of g++ for the following code:

// 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.

__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&).

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).

筱武穆 2024-10-16 03:28:38

我认为在 class 块之外完成初始化的主要原因是允许使用其他类成员函数的返回值进行初始化。如果您想使用 b::some_static_fn() 初始化 a::var,您需要确保每个 .cpp 文件首先包括 ah 包括 bh。这会很混乱,特别是当(迟早)您遇到循环引用时,您只能使用不必要的接口来解决。同样的问题是在 .cpp 文件中实现类成员函数而不是将所有内容都放在主类的 .h 中的主要原因。

至少对于成员函数,您可以选择在标头中实现它们。对于变量,您必须在 .cpp 文件中进行初始化。我不太同意这种限制,而且我认为也没有充分的理由。

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.

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文