Until death do us part or all about static in C ++





Hello. At one of the code reviews, I came across the idea that many, and what to hide myself, not that we understand well when to use the static keyword . In this article, I would like to share my knowledge and information regarding the static keyword . , , ++. /++ . , static ++, . ++, , , , .



static?



Static is a C ++ keyword used to give an element a special characteristic. For static elements, memory is allocated only once and these elements exist until the program terminates. All these elements are stored not in the heap or on the stack, but in special memory segments called .data and .bss (depending on whether static data is initialized or not). The picture below shows a typical layout for program memory.





Where is it used?



Below is a diagram of how and where static is used in a program.







And now I will try to describe in detail everything that is shown in the diagram. Go!



Static variables inside a function



Static variables, when used inside a function, are initialized only once, and then they retain their value. These static variables are stored in a static memory area ( .data or .bss ) rather than on the stack, which allows the value of the variable to be stored and used throughout the life of the program. Let's take a look at two nearly identical programs and their behavior. The only difference is that one uses a static variable, and the other does not.



First program:



#include <iostream>

void counter() {
  static int count = 0; //  4
  std::cout << count++;
}

int main() {
  for (int i = 0; i < 10; ++i) {
    counter();
  }
  return 0;
}


Program output:

0123456789


Second program:



#include <iostream>

void counter() {
  int count = 0; //  4
  std::cout << count++;
}

int main() {
  for (int i = 0; i < 10; ++i) {
    counter();
  }
  return 0;
}


Program output:

000000000


If you don't use static on line 4 , the memory allocation and initialization of the variable count occurs each time counter () is called , and is destroyed each time the function exits. But if we make the variable static, after initialization (the first time the counter () function is called ), count will be scoped to the end of the main () function , and the variable will keep its value between calls to counter () .



Static class objects



A static object of a class has the same properties as a regular static variable described above, i.e. stored in .data or .bss memory segment, created at startup and destroyed at program termination, and initialized only once. The object is initialized as usual - through the class constructor. Let's consider an example with a static class object.



#include <iostream>

class Base { //  3
public:
  Base() { //  5
    std::cout << "Constructor" << std::endl;
  }
  ~Base() { //  8
    std::cout << "Destructor" << std::endl; 
  }
};

void foo() { 
  static Base obj; //  14
} //  15

int main() {
  foo(); //  18
  std::cout << "End of main()" << std::endl;
  return 0;
}


Program output:

Constructor

End of main ()

Destructor


3 Base ( 5) ( 8). . 14 obj Base. foo() 18.



- , , foo() 15, , .. . , , .





#include <iostream>

class Base {
public:
  Base() {
    std::cout << "Constructor" << std::endl;
  }
  ~Base() { 
    std::cout << "Destructor" << std::endl; 
  }
};

void foo() { 
  Base obj; 
} //  15

int main() {
  foo();
  std::cout << "End of main()" << std::endl;
  return 0;
}


If we remove static when creating a variable in the foo () function , then the destruction of the object will occur on line 15 each time the function is called. In this case, the output of the program will be quite expected for a local variable with allocated memory on the stack:

Constructor

Destructor

End of main ()




Static class members



Compared to previous use cases, static members of a class are a little more difficult to understand. Let's see why. Suppose we have the following program:



#include <iostream>

class A { //  3
public:
  A() { std::cout << "Constructor A" << std::endl; }
  ~A() { std::cout << "Destructor A" << std::endl; }
};

class B { //  9
public:
  B() { std::cout << "Constructor B" << std::endl; }
  ~B() { std::cout << "Destructor B" << std::endl; }

private:
  static A a; //  15 ()
};

int main() {
  B b; //  19
  return 0;
}


In our example, we created class A (line 3) and class B (line 9) with static class members ( line 15 ). We assume that creating object b on line 19 will create object a on line 15 . This would be the case if we used non-static members of the class. But the output of the program will be as follows:

Constructor B

Destructor B


The reason for this behavior is that the static members of a class are not initialized with a constructor because they do not depend on the initialization of the object. Those. on line 15, we are only declaring the object, not defining it, since the definition must happen outside the class using the scope resolution operator (: :) . Let's define a class B members .



#include <iostream>

class A {
public:
  A() { std::cout << "Constructor A" << std::endl; }
  ~A() { std::cout << "Destructor A" << std::endl; }
};

class B {
public:
  B() { std::cout << "Constructor B" << std::endl; }
  ~B() { std::cout << "Destructor B" << std::endl; }

private:
  static A a; //  15 ()
};

A B::a; //  18 ()

int main() {
  B b;
  return 0;
}


Now, after we have defined our static class member on line 18, we can see the following program output:

Constructor A

Constructor B

Destructor B

Destructor A


It must be remembered that the class member will be the same for all instances of class B , i.e. if we created three objects of class B , then the constructor of the static class member will be called only once. Here's an example of what I'm talking about:



#include <iostream>

class A {
public:
  A() { std::cout << "Constructor A" << std::endl; }
  ~A() { std::cout << "Destructor A" << std::endl; }
};

class B {
public:
  B() { std::cout << "Constructor B" << count++ << std::endl; }
  ~B() { std::cout << "Destructor B" << --count << std::endl; }

private:
  static A a; // 
  static int count; // 
};

A B::a; // 
int B::count = 1; // 

int main() {
  B b1, b2, b3;
  return 0;
}


Program output:

Constructor A

Constructor B1

Constructor B2

Constructor B3

Destructor B3

Destructor B2

Destructor B1

Destructor A


Static functions



Static functions came to C ++ from C. By default, all functions in C are global, and if you want to create two functions with the same name in two different .c (.cpp) files of the same project, you will get an error stating that this function already defined ( fatal error LNK1169: one or more multiply defined symbols found ). Below is a listing of three files of one program.



// extend_math.cpp
int sum(int a, int b) {
  int some_coefficient = 1;
  return a + b + some_coefficient;
}


// math.cpp
int sum(int a, int b) {
  return a + b;
}


// main.cpp
 int sum(int, int); // declaration

int main() {
  int result = sum(1, 2);
  return 0;
}


In order to fix this problem, we will declare one of the functions static. For example this one:



// extend_math.cpp
static int sum(int a, int b) {
  int some_coefficient = 1;
  return a + b + some_coefficient;
}


, , . sum() math.cpp . , static , , , , , (.h).



, inline static, , . (.cpp), #include , . , , .. include .cpp .





- ()



You can use a static member function without creating an object of the class. Static functions are accessed using the class name and the scope resolution operator (: :) . When using a static member function, there are limitations such as:



  1. Within a function, you can only access static data members, other static member functions, and any other function outside the class.
  2. Static member functions have the scope of the class in which they reside.
  3. You do not have access to the this pointer of the class, because we do not create any object to call this function.


Let's take a look at the following example:



#include <iostream>

class A {
public:
  A() { std::cout << "Constructor A" << std::endl; }
  ~A() { std::cout << "Destructor A" << std::endl; }

  static void foo() { //  8
    std::cout << "static foo()" << std::endl;
  }
};

int main() {
  A::foo(); //  14
  return 0;
}


In class A, on line 8 , we have a static member function foo () . On line 14 , we call the function using the class name and scope resolution operator, and we get the following program output:

static foo ()


From the output, you can see that there is no object creation and no constructor / destructor is called.



If the foo () method were non-static, the compiler would throw an error on the expression on line 14 , because you need to create an object in order to access its non-static methods.





Conclusion



โ€“ ยซ static , ยป. , , .



:



  • , . , , , . , , .
  • , , .. , . , , .. .
  • static Singleton, , . , -. Singleton , , .
  • Sometimes, in order for a function to run only once without storing the previous state somewhere in the object, static variables are used. You can see an example in the section "Static variables inside a function". But this is not a very good approach, and can lead to long hours of bug hunting if you are using multithreading.
  • In practice, C ++ programmers often use static member functions as an alternative to regular functions that don't require an object to be created to execute.


I hope you enjoyed my article on the static keyword in C ++. I would be glad to any criticism and advice. Thanks to all!



All Articles