Friday, June 21, 2019

The static keyword and its various uses in C++



The keyword static is one which has several meanings in C++ that I find very confusing and I can never bend my mind around how its actually supposed to work.




From what I understand there is static storage duration, which means that it lasts for the lifetime of the program in the case of a global, but when you're talking about a local, it means that it's zero initialized by default.



The C++ Standard says this for class data members with the keyword static:



3.7.1 Static storage duration [basic.stc.static]




3 The keyword static can be used to declare a local variable with static storage duration.




4 The keyword static applied to a class data member in a class definition gives the data member static storage duration.




What does it mean with local variable? Is that a function local variable? Because there's also that when you declare a function local as static that it is only initialized once, the first time it enters this function.



It also only talks about storage duration with regards to class members, what about it being non instance specific, that's also a property of static no? Or is that storage duration?



Now what about the case with static and file scope? Are all global variables considered to have static storage duration by default? The following (from section 3.7.1) seems to indicate so:





1 All variables which do not have dynamic storage duration, do not have thread storage duration, and are not local have static storage duration. The storage for these entities shall last for the duration of the program (3.6.2, 3.6.3)




How does static relate to the linkage of a variable?



This whole static keyword is downright confusing, can someone clarify the different uses for it English and also tell me when to initialize a static class member?


Answer





static variables exist for the "lifetime" of the translation unit that it's defined in, and:





  • If it's in a namespace scope (i.e. outside of functions and classes), then it can't be accessed from any other translation unit. This is known as "internal linkage" or "static storage duration". (Don't do this in headers except for constexpr. Anything else, and you end up with a separate variable in each translation unit, which is crazy confusing)

  • If it's a variable in a function, it can't be accessed from outside of the function, just like any other local variable. (this is the local they mentioned)

  • class members have no restricted scope due to static, but can be addressed from the class as well as an instance (like std::string::npos). [Note: you can declare static members in a class, but they should usually still be defined in a translation unit (cpp file), and as such, there's only one per class]



locations as code:



static std::string namespaceScope = "Hello";

void foo() {
static std::string functionScope= "World";
}
struct A {
static std::string classScope = "!";
};


Before any function in a translation unit is executed (possibly after main began execution), the variables with static storage duration (namespace scope) in that translation unit will be "constant initialized" (to constexpr where possible, or zero otherwise), and then non-locals are "dynamically initialized" properly in the order they are defined in the translation unit (for things like std::string="HI"; that aren't constexpr). Finally, function-local statics will be initialized the first time execution "reaches" the line where they are declared. All static variables all destroyed in the reverse order of initialization.




The easiest way to get all this right is to make all static variables that are not constexpr initialized into function static locals, which makes sure all of your statics/globals are initialized properly when you try to use them no matter what, thus preventing the static initialization order fiasco.



T& get_global() {
static T global = initial_value();
return global;
}


Be careful, because when the spec says namespace-scope variables have "static storage duration" by default, they mean the "lifetime of the translation unit" bit, but that does not mean it can't be accessed outside of the file.






Significantly more straightforward, static is often used as a class member function, and only very rarely used for a free-standing function.



A static member function differs from a regular member function in that it can be called without an instance of a class, and since it has no instance, it cannot access non-static members of the class. Static variables are useful when you want to have a function for a class that definitely absolutely does not refer to any instance members, or for managing static member variables.



struct A {
A() {++A_count;}
A(const A&) {++A_count;}
A(A&&) {++A_count;}

~A() {--A_count;}

static int get_count() {return A_count;}
private:
static int A_count;
}

int main() {
A var;


int c0 = var.get_count(); //some compilers give a warning, but it's ok.
int c1 = A::get_count(); //normal way
}


A static free-function means that the function will not be referred to by any other translation unit, and thus the linker can ignore it entirely. This has a small number of purposes:




  • Can be used in a cpp file to guarantee that the function is never used from any other file.

  • Can be put in a header and every file will have it's own copy of the function. Not useful, since inline does pretty much the same thing.


  • Speeds up link time by reducing work

  • Can put a function with the same name in each translation unit, and they can all do different things. For instance, you could put a static void log(const char*) {} in each cpp file, and they could each all log in a different way.


No comments:

Post a Comment

plot explanation - Why did Peaches' mom hang on the tree? - Movies & TV

In the middle of the movie Ice Age: Continental Drift Peaches' mom asked Peaches to go to sleep. Then, she hung on the tree. This parti...