C++: Elegant construction of global variables

First of all, let me declare that the global variable mentioned in the title refers to the requirement: we need an object that can be used globally.

simple construction

Generally, we first think of using global static variables, that is, global variables, namespace scope variables, and variables declared as static in classes and in file scope, so that we can use them elsewhere in the file or even in other compilation modules It (of course excluding variables declared static at file scope). as follows:

global.hpp:

//全局变量 声明 
extern int a;

//file作用域变量声明为static 声明 错误!!!其实是定义 
//static int b;

//namespace作用域变量 声明 
namespace Test{
	extern int c;
};

//classes内声明为static变量 声明 
class Test_C{
public:
	static int d;
};

golobal.cpp:

#include "Golobal.hpp" 

//全局变量 定义 
int a = 3;

//file作用域变量声明为static 定义 
static int b = 3;

//namespace作用域变量 定义 
int Test::c= 3;

//classes内声明为static变量 定义 
int Test_C::d = 3;

 

main.cpp

#include <iostream>
#include "Golobal.hpp"
using namespace std;

int main(int argc, char *argv[])
{
	cout<<"a:"<<a<<" c:"<<Test::c<<" d:"<<Test_C::d;
	return 0;
}

operation result:

 

question

But we have to be aware of a problem, that is, the initialization order of global static variables in different compiled modules in C++ is uncertain, that is to say, if two global static variables are defined in different files, and one of the variables A The initialization needs to depend on the initialization of another variable B (this also means that your design is not very good, but sometimes we have to implement it like this), then there may be inexplicable problems, because the compiler cannot guarantee that A and B initialization sequence, then B may not be initialized when A is initialized, thus using uninitialized B, which is very dangerous. Here is a very simple example:

int a = 3;
int b = a;

static int c = b;
void main(void){
    use(c);
    return;
}

There is no problem with putting the above code in the same file, and the value of b can be guaranteed to be 3, but if the definitions of a and b are placed in different implementation files, there is no guarantee that the value of b must be the value of a after initialization , which is 3 (of course your compiler can easily get the correct answer, but don't write this kind of code that depends on the compilation order), we can analyze it like this, int a=3; it is actually divided into two step, definition and initialization, the definition allocates a block of memory formally for the symbol a, has a virtual memory address and occupied size (assuming 0x10000), and these allocation operations are performed at compile time (of course formally , the specific runtime also needs to be loaded into the real memory), so that during the program link period, other compilation modules can use this global variable after declaration without reporting an undefined error, and the initialization is during the compilation and link period An instruction similar to copy 0x10000 0x03 (write the immediate value 3 into the memory at address 0x10000) is generated. After this instruction is executed, the memory will be 3, otherwise it is a random value, and then the execution of this instruction needs to wait until the program is running , the same is true for b, so if the assignment statement b=a runs after copy 0x10000 0x03, then we can get the correct initial value, otherwise we can only get an uninitialized value.

optimization

So how to get the value that is guaranteed to be initialized? Here we take advantage of a guarantee of C++: a local static object in a function (an object declared as static in a function) will be initialized when the definition of the object is first encountered during the function is called. We can analyze it in this way. The storage area of ​​the static object is located in the static area. Similarly, at compile time, if there is a call to the function, the symbol of the local static object in the function has been bound and mapped to a piece of memory in the static area. It’s just that its scope is within the function, and we know that calling a function is actually calling a subroutine, that is, the code within the function. Then this guarantee of C++ means that when we define the local static object for the first time in the function , if it is not initialized, the object will be mapped to a piece of memory with an area value of 0, which is what we call the default initialization value of 0 (integer static variable). If we specify initialization, it will be the first in the function by default Insert an initialization instruction before the instructions following the subdefinition to ensure that the object is initialized, so that when we call the function, we can guarantee the initialized value.

So a better way to construct a global object should be to define a global static variable as a local static variable and return it through a function. If you want to change the value of the object, return the reference of the object, otherwise directly press Just return the value. As for this function, you can define it in the way of C, or define it as a static function in the class. as follows:

Golobal.hpp:
int& a();
	
class Test_C{
public:
	static int b();	
};

Golobal.cpp:
#include "Golobal.hpp" 
int& a(){
	static int a = 3;
	return a;	
}
int Test_C::b(){
	static int b = a();
	return b;
}

main.cpp:
#include <iostream>
#include "Golobal.hpp"
using namespace std;

int main(int argc, char *argv[])
{
	cout<<"a:"<<a()<<" b:"<<Test_C::b()<<endl;
	int& c = a();
	c = 4;
	cout<<"a:"<<a()<<" b:"<<Test_C::b()<<endl;
	return 0;
}

operation result:

 

another benefit

In fact, there is another advantage of using global variables in this way, that is, when you do not call the function in the program, then there will be no construction and destruction costs of the local static variable, and another advantage is that we can use Customize some operations when using this global variable, because it is essentially a function, and it may be a little difficult for you to understand this. For example, I now have a requirement: the program has a global variable that can be used by any other module, and requires When using it for the first time, use the standard input to assign a value, and the assignment range is 0~100, so that I can perform different operations according to the value in the later part of the program, such as 1~20 what to do, 20~50 to do What, what do 50~100 do, and the value will not be changed in the subsequent use process (the process of standard input will no longer be performed).

If we use the simple way of defining global variables mentioned at the beginning of this article, in addition to the problem of dependence on the initialization order of global variables, there will be another problem, that is, we need to find the place where the program uses the variable for the first time, and then add Go to the standard input, but sometimes it is not easy to find, and even in different situations, the place where it is used for the first time is different. Another solution is to judge whether the control logic has been input. If there is no input, then Just carry out the logic of standard input, but this logic code must be added every time the variable is used. Isn’t it very troublesome and not easy to maintain. In fact, I believe readers have already thought of using module programming. The method is to define a module to obtain the global variable and go around. This is actually the benefit we mentioned above: customizing some operations when using the global variable. as follows:

golobal.hpp
int a();

golobal.cpp
#include <iostream> 
#include <assert.h>
#include "golobal.hpp" 
int a(){
	static int a = -1;
	if (a==-1){
		std::cout<<"input a:";
		std::cin>>a;
		assert(a>=0 && a<=100);
	}
	return a;	
}

main.cpp:
#include <iostream>
#include "golobal.hpp"
using namespace std;

int main(int argc, char *argv[])
{
	a();
	cout<<"a:"<<a()<<endl;
	return 0;
}

operation result:

Guess you like

Origin blog.csdn.net/g1093896295/article/details/103225655
Recommended