I'm trying to initialize a global map
std::map<long, std::string> Global_ID_Mapper;
with a number of "init" classes like:
struct AGlobalMapperInitializer
{
AGlobalMapperInitializer()
{
Global_ID_Mapper.insert( std::make_pair(1, "Value1") );
Global_ID_Mapper.insert( std::make_pair(2, "Value2") );
}
};
I want to fill the map automatically during application start. So in one of my cpp files, I just define a global variable of that "init" class.
// AGlobalMapperInitializer.cpp
AGlobalMapperInitializer AGlobalMapperInitializer_Value;
The Mapper filling is a side effect of the AGlobalMapperInitializer_Value
creation.
The problem is that the cpp is obviously ignored by linker if the cpp doesn't contain anything except this global variable. When I put some useful other code into the cpp (or define the global initializer in some non-empty cpp), the constructor is invoked and the global mapper is filled. But if the cpp contains only the global that isn't referenced in no other file, the cpp is compiled, the obj file contains the variable, but the linker doesn't mention it during the link and it is missed in the exe.
How can I insist on linking the cpp into exe?
Is there some pragma or dummy code to put into the cpp to get it non-ignored?
I use the Visual Studio 2012.
C++ does not require initialisation of a global variable x
to take place if no function or variable from the same file (actually translation unit) as x
is ever referenced.
See C++11 [basic.start.init]§4
:
It is implementation-defined whether the dynamic initialization of a non-local variable with static storage duration is done before the first statement of main
. If the initialization is deferred to some point in time after the first statement of main
, it shall occur before the first odr-use (3.2) of any function or variable defined in the same translation unit as the variable to be initialized.
So to force the variable initialisation, you have to put it into a file whose other contents you actually use, or directly use the variable somewhere.
The linker doesn't include the variable and it's initializer when it is not referenced. You can create a reference to this variable inside your code:
int main()
{
printf("", &AGlobalMapperInitializer_Value));
}
If you want to avoid this contamination of the source code you cause the same effect with the /INCLUDE
argument of the linker. You have to add the decorated name that you can take from the .map file when you have tried the hack above.
The VS2010 offers this options as Force Symbol Reference
in the project properties: Configuration Properies -> Linker -> Input. I hope it's the same for VS2012.
Thanks for both Angew and harper that helped to find the solution.
There are 2 possible solutions:
The portable one (but not good because of its performance).
The initializer instance can be defined not in the dedicated cpp file, but in the h, using the word static
.
// AGlobalMapperInitializer.h
// It wasn't mentioned before that this h file is included in many cpp files
struct AGlobalMapperInitializer
{
AGlobalMapperInitializer()
{
if( !Global_ID_Mapper.insert( std::make_pair(1, "Value1") ).second )
return;
Global_ID_Mapper.insert( std::make_pair(2, "Value2") );
}
};
static AGlobalMapperInitializer AGlobalMapperInitializer_Value;
Since the variable is static
, the AGlobalMapperInitializer_Value
is created separately in all the cpp files that include the h file. Each of these variables is trying to add values to the global mapper. Definitely, only one of them succeed. The performance problem is partially resolved by checking result of the first insert. If the first value was already inserted - no need to try other values.
Note: It is assumed that all these instances will be created within the same thread, otherwise the Global Mapper should implement synchronization of the insert calls.
The compiler-specific solution (Visual Studio).
The linker can be forced to keep class by adding #pragma comment (linker, "/include:<decorated name>")
. The decorated name can be a class constructor or any other function. The issue is that specifying decorated name hard-code isn't good nor convenient. The decoration method can change with compiler upgrade. So, here the __FUNCDNAME__
can be used.
struct AGlobalMapperInitializer
{
AGlobalMapperInitializer()
{
// Make sure the class will not be threw away by linker
#pragma comment (linker, "/include:"__FUNCDNAME__)
Global_ID_Mapper.insert( std::make_pair(1, "Value1") );
Global_ID_Mapper.insert( std::make_pair(2, "Value2") );
}
};
Fortunately, the #pragma
can be specified inside the class constructor.