Search code examples
c++header-files

Global constants undefined in constructor


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.


Solution

  • 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!