C ++ static member variable and its initialization


For static member variables in a C ++ class, initialization is performed outside the class. I wonder why? Any logical reasoning / limitation for this? Or is it a purely inherited implementation that the standard does not want to fix?

I think that initializing in a class is more "intuitive" and less confusing. It also gives a feeling of both a static and a global variable. For example, if you see a static member const.

+42
c ++ initialization static-variables
Dec 28 '10 at 16:46
source share
5 answers

In essence, this is due to the fact that static members must be defined in only one translation unit so as not to violate the One-Definition Rule rule . If the language should have been something like:

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

then name will be defined in each translation unit that #include this header file.

C ++ allows you to define integral static members in a declaration, but you should still include the definition within the same translation unit, but it's just a shortcut or syntactic sugar. Thus, it is allowed:

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

As long as a) the expression const an integer or enumerated type, b) the expression can be evaluated at compile time, and c) there is still a definition that does not violate one rule of definition:

file: gizmo.cpp

 #include "gizmo.h" const int Gizmo::count; 
+36
Dec 28 '10 at 17:02
source share

In C ++, since the beginning of time, the presence of an initializer has been an exclusive attribute of the definition of an object, that is, a declaration with an initializer is always a definition (almost always).

As you should know, each external object used in a C ++ program should be defined once and only once in only one translation unit. Allowing initializers in a class for static objects immediately contradicts this convention: initializers get into the header files (where class definitions are usually located) and, thus, generate several definitions of the same static object (one for each translation unit, which includes the header file). This, of course, is unacceptable. For this reason, the declaration approach for static class members is left completely โ€œtraditionalโ€: you declare it only in the header file (i.e., no initializer is allowed), and then you define it in the translation units of your choice (possibly with an initializer )

The only exception to this rule was made for constant static members of the class of integral or enumerated types, since such records can be used for integral constant expressions (ICE). The main idea of โ€‹โ€‹ICE is that they are evaluated at compile time and therefore are independent of the definitions of the objects involved. That is why this exception was possible for integral or enumerated types. But for other types, this simply contradicts the basic principles of C ++ declaration / definition.

+11
Dec 28 '10 at 17:22
source share

This is because of how the code compiles. If you were to initialize it in a class, which is often in the header, every time the header is included, you will get an instance of a static variable. This is definitely not an intention. Initialization outside the class gives you the ability to initialize it in a cpp file.

+2
Dec 28 '10 at 16:52
source share

Section 9.4.2, Static Data Members, C ++ Standard States:

If the static data member is of type const integer or const enumeration, its declaration in the class definition may indicate a const initializer, which must be an integral constant expression.

Consequently, the value of a static data member can be included "inside the class" (by which I assume that you are referring to the class declaration). However, the type of the static data element must be an enum type const integer or const . The reason that the values โ€‹โ€‹of static data elements of other types cannot be specified in the class declaration is because non-trivial initialization is required (i.e., the constructor must be executed).

Imagine if the following rules were:

 // my_class.hpp #include <string> class my_class { public: static std::string str = "static std::string"; //... 

Each object file corresponding to the CPP files that include this header will have not only a copy of the storage space for my_class::str (consisting of sizeof(std::string) bytes), but also a "ctor section" that calls std::string constructor with a C-string. Each copy of the storage space my_class::str will be identified using a common label, so the linker can theoretically merge all copies of the memory space into one. However, the linker will not be able to isolate all copies of the constructor code inside the ctor sections of the object files. This would be like a linker request to remove all the code to initialize str in compiling 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 output of the g ++ assembler 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 doing:

 g++ -S SO4547660.cpp 

Looking through the SO4547660.s file generated by g ++, you can see that there is a lot of code for such a small source file.

__ZN8my_class3strE is the storage space label my_class::str . There is also a source for the assembly of the __static_initialization_and_destruction_0(int, int) function, which is labeled __Z41__static_initialization_and_destruction_0ii . This function is specifically for g ++, but just know that g ++ will make sure that it is called before any code is run without an initializer. Note that the implementation of this function calls __ZNSsC1EPKcRKSaIcE . This is a garbled character for std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&) .

Returning to the hypothetical example above and using this data, each object file corresponding to the CPP file that includes my_class.hpp will have the __ZN8my_class3strE label for sizeof(std::string) bytes, as well as the assembly code for calling __ZNSsC1EPKcRKSaIcE as part of the __static_initialization_and_destruction_0(int, int) function __static_initialization_and_destruction_0(int, int) . The compiler can easily combine all occurrences of __ZN8my_class3strE , but it cannot isolate the code that calls __ZNSsC1EPKcRKSaIcE in the implementation of the __static_initialization_and_destruction_0(int, int) object file.

+1
Dec 28 '10 at 17:40
source share

I think that the main reason for initializing outside the class block is the ability to initialize with the return values โ€‹โ€‹of other member functions of the class. If you would like to initialize a::var with b::some_static_fn() , you need to make sure that every .cpp file that includes ah includes bh . It would be a mess, especially when (sooner or later) you came across a circular link that you could only resolve with an unnecessary interface . The same problem is the main reason for implementing class member functions in a .cpp file instead of putting everything in your main .h class.

At least with member functions you have the option to implement them in the header. With variables, you must initialize in the .cpp file. I do not quite agree with this limitation, and I do not think that there is a good reason for this.

0
Dec 28 '10 at 16:59
source share



All Articles