I have two files for constants:
// constants.h
extern const std::string testString;
// constants.cpp
const std::string testString = "defined!";
I need to use this constant in the constructor of an object when the program is initialised, but it comes as undefined. The code in the constructor is:
MyClass::MyClass() {
printf("Value of test string: %s", testString.c_str());
}
// output:
Value of test string: (null)
The class and the constants are defined in the same namespace, and it does not give me an error that the constant is undefined. After the object is initialised (e.g. with a hard-coded string), it works fine and prints out the value of the constant ("defined!"). Primitive constants seem to work fine in the constructor.
I think it has something to do with the constant not being initialised at that time (so from the .cpp file). Do you know why this might happen? Does the initialisation of extern const occur after the program fully initialises?
Thank you in advance
EDIT:
Note that the string
type is to simplify the issue, so converting it to char
is not an option as I am also interested in having other non-primitive types too.
Code for a minimal dirty example of a program that shows this issue:
// constants.h
extern const std::string testString;
// constants.cpp
#include "constants.h"
const std::string testString = "defined!";
// MyClass.h
class MyClass {
public:
MyClass();
virtual ~MyClass();
};
// MyClass.cpp
#include "MyClass.h"
#include "constants.h"
MyClass::MyClass() {
printf("Value of test string: %s\n", testString.c_str());
}
MyClass::~MyClass() {}
// main.cpp
#include "MyClass.h"
#include "constants.h"
MyClass my; // undefined string (outputs null)
int main(int argc, char** argv) {
MyClass my; // defined string
return 0;
}
EDIT 2:
The solution in this case was to define an static inline function in the header file, as @Brian and @LightnessRacesinOrbit suggested. They both contributed to the final answer.
Here is the code:
inline std::string getTestString() { return "defined!"; }
This allows to have non constexpr
types as global constants.
Within the constants.cpp
translation unit, testString
will be initialized before any subsequently defined non-local variable. Between translation units, we have what's called the "static initialization order fiasco"; any translation unit other than constants.cpp
cannot assume that testString
has been initialized until after main
has started executing, so if it tries to read its value during one of its own non-local initializations, then it may observe a zero-initialized std::string
object, which has undefined behaviour.
My suggestion to avoid this problem, which is also the rule followed at my former workplace, is that if you must have global constants, make them constexpr
if possible, and be very wary of having any non-constexpr
global variables. std::string
isn't constexpr
(yet), but an old-fashioned char
array could work:
// constants.h
inline constexpr char testString[] = "defined!";
// constants.cpp
// no need to define `testString` here!