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 includes
super.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