I'm developing a library of objects and functions and have a header file, here named super.hpp, that contains some initialization tasks.
super.hpp
#ifndef H_INIT
#define H_INIT
#include <iostream>
#include <string>
static bool isInit = false;
struct settings_struct{
std::string path = "foo";
void load(){ path = "bar"; }
};
struct initializer_struct{
settings_struct settings;
initializer_struct(){
if(!isInit){
std::cout << "Doing initialization\n";
settings.load();
isInit = true;
}
// settings.load();
}//====================
~initializer_struct(){
if(isInit){
std::cout << "Doing closing ops\n";
isInit = false;
}
}
};
static initializer_struct init; // static declaration: only create one!
#endif
My intention with this header is to create the initializer_struct object once; when it is constructed, this struct performs a few actions that set flags and settings for the entire library. One of these actions is the creation of settings struct that loads settings from an XML file; this action should also occur only once when the init struct is constructed, so the variables (here, path) are saved from the settings file. The super.hpp header is included in all objects in the library because different objects are used in different capacities, i.e., there's no way to predict which ones will be used in an application, so I include the super.hpp header in all of them to guarantee it is called no matter which objects are used.
My problem is this: when I include super.hpp in multiple classes/objects that are all loaded by the main application, the struct init appears to be re-initialized and the variables set when the settings_struct is constructed are overwritten with the default values. To see this in action, consider these additional files:
test.cpp
#include "classA.hpp"
#include "classB.hpp"
#include <iostream>
int main(int argc, char *argv[]){
(void) argc;
(void) argv;
classA a;
classB b;
std::cout << "Settings path = " << init.settings.path << std::endl;
std::cout << "Class A Number = " << a.getNumber() << std::endl;
std::cout << "Class B Number = " << b.getInteger() << std::endl;
}
classA.hpp
#ifndef H_CLASSA
#define H_CLASSA
class classA{
private:
double number;
public:
classA() : number(7) {}
double getNumber();
};
#endif
classA.cpp
#include "super.hpp"
#include "classA.hpp"
double classA::getNumber(){ return number; }
classB.hpp
#ifndef H_CLASSB
#define H_CLASSB
class classB{
private:
int number;
public:
classB() : number(3) {}
int getInteger();
};
#endif
classB.cpp
#include "super.hpp"
#include "classB.hpp"
int classB::getInteger(){ return number; }
To compile and run the example,
g++ -std=c++11 -W -Wall -Wextra -Weffc++ -pedantic -c classA.cpp -o classA.o
g++ -std=c++11 -W -Wall -Wextra -Weffc++ -pedantic -c classB.cpp -o classB.o
g++ -std=c++11 -W -Wall -Wextra -Weffc++ -pedantic classA.o classB.o test.cpp -o test.out
./test.out
I expect the output from test.out to be the following:
Doing initialization
Settings path = bar
Number = 7
Doing closing ops
However, when I run this, I instead get "Settings path = foo". Thus, my conclusion is that the initializer_struct, init, is being constructed more than once. The first time, the boolean isInit is false, and the settings structure load function sets path to "bar." For all subsequent initializations, isInit is true, so the load function is not called again and it seems that the variable values from the uninitialized settings (i.e., path = "foo") overwrite the previously loaded values, hence the output of init.settings.path in test.cpp.
Why is this? Why is the init object constructed every time the header is included? I would have thought the include guards would keep the header code from being called multiple times. If I make the init variable in test.hpp a non-static variable, then multiple copies are created and the output prints multiple iterations of "Doing initialization" and "Doing closing ops." Additionally, if I uncomment the settings.load() function call outside the conditional statement in the initializer_struct() constructor, then the output gives a settings path of "bar". Finally, removing the inclusion of super.hpp from classA.cpp results in a path value of "bar", further supporting my hypothesis that multiple inclusions of test.hpp result in multiple constructor calls.
I would like to avoid having settings.load()' called for every object that includessuper.hpp` - that's why I placed the command within the conditional statement. Any thoughts? How to I make sure the settings file is read only once and that the values loaded are not overwritten? Is this a completely obtuse method to set some flags and settings that my library uses? If so, do you have any suggestions to make the processes simpler and/or more elegant?
Thanks!
Edit: Updated to include two object classes to closer represent my more complex setup