15

After reading a lot of the questions regarding initialization of static variables I am still not sure how this applies to const variables at namespace level.

I have kind of the following code in a header file config.h generated by the build script:

static const std::string path1 = "/xyz/abc";
static const std::string path2 = "/etc";

According to what I have read the static keyword is not necessary, even deprecated here.

My Question: Is the code above prone to the static initialization fiasco?

If I have the following in a header file myclass.h:

class MyClass
{
public:
    MyClass(const std::string& str) : m_str(str) {}
    std::string Get() const { return m_str; }

private:
    std::string m_str;
}

const MyClass myclass1("test");

Will this pose any problems with static initialization?

If I understood right, due to const variables having internal linkage there should be no problem in both cases?

Edit: (due to dribeas answer)

Maybe I should mention that I am interested in use cases like:

In main.cpp:

#include <config.h>
#include <myclass.h>

std::string anotherString(path1 + myclass1.Get());

int main()
{
    ...
}

Another question regarding this use case: Will the compiler optimize away path2 in this case?

Hannah S.
  • 3,081
  • 3
  • 20
  • 27

4 Answers4

13

Your first definition places path1 in each compilation unit that includes config.h. To avoid this, don't define variables in header files. Usually you'd declare the variables in the header as extern:

extern const std::string path1;
extern const MyClass myclass1;

and define them in a translation unit, e.g. config.cpp:

const std::string path1 = "/xyz/abc";
const MyClass myclass1("test");

Sometimes you need a constant variable that is usable only from one translation unit. Then you can declare that variable at file scope as static.

static const std::string path1 = "/xyz/abc";

static is not deprecated any more. static and extern are sometimes implied, but I always forget where and how, so I usually specify them explicitly for all namespace-level variables.

Mattias Backman
  • 927
  • 12
  • 25
Philipp
  • 48,066
  • 12
  • 84
  • 109
  • 1
    It's not true that `static` can only be used in implementation files. – Rob Kennedy Jan 26 '11 at 17:03
  • of course, there is no concept of implementation file in C++ at all, I'll try to change the wording – Philipp Jan 26 '11 at 17:06
  • 2
    I think the phrase you're looking for is "translation unit" instead of implementation file. Values should be declared in headers and defined in at most one translation unit. In this case, since they are const and have implied internal linkage, you don't get a multiply defined symbol error at link time, but you do have the same symbol defined in every translation unit that includes this header. Since they have internal linkage, they don't break linking, but the compiler may not eliminate the duplicates. I once shaved 8 MB off an executable by removing definitions of strings from headers. – legalize Jan 31 '15 at 23:19
12

I tried to get the necessary information right from the C++03 Standard document. Here is what I found:

Regarding the const static declarations:

According to section 3.5.3 objects defined at namespace level and declared const have internal linkage by default. static also declares a namespace level object to have internal linkage so there is no need to declare an object static const.

Also according to Annex D.2

The use of the static keyword is deprecated when declaring objects in namespace scope (see 3.3.5).

Regarding the static initialization fiasco:

Since the variables are defined in a header file they are always defined before any other static objects using them.

From section 3.6.2.1:

Objects with static storage duration defined in namespace scope in the same translation unit and dynamically initialized shall be initialized in the order in which their definition appears in the translation unit.

Answer 1: This means passing the variables to a static object constuctor should be fine.

Answer 2: However a problem could occur if the variables are referenced from a non-inline constructor of a static object:

Neither in section 3.6.2.1 nor 3.6.2.3 is it specified in which order the static objects in different compilation units are initialized if dynamic initialization is done before the first statement of main.

Consider the following:

// consts.h
#include <string>

const std::string string1 = "ham";
const std::string string2 = "cheese";

// myclass.h
#include <string>

class MyClass
{
public:
    MyClass();
    MyClass(std::string str);
    std::string Get() { return memberString; }
private:
    std::string memberString;
}

// myclass.cpp
#include "consts.h"
#include "myclass.h"

MyClass::MyClass() : memberString(string1) {}

MyClass::MyClass(std::string str) : memberString(str) {}

// main.cpp
#include <iostream>
#include "consts.h"
#include "myclass.h"

MyClass myObject1;
MyClass myObject2(string2);

using namespace std;

int main()
{
    cout << myObject1.Get(); // might not print "ham"
    cout << myObject2.Get(); // will always print "cheese"
}

Since myclass.cpp has its own copy of the const variables, these might not be initialized when MyClass::MyClass() is called.

So yes, const variables defined in header files can be used in a way that is prone to the static initialization fiasco

As far as I can see this does only apply to variables not requiring static initialization:

From C++03 standard, section 3.6.2.1:

Objects of POD types (3.9) with static storage duration initialized with constant expressions (5.19) shall be initialized before any dynamic initialization takes place.

Hannah S.
  • 3,081
  • 3
  • 20
  • 27
8

What is referred as the static initialization fiasco is a problem when one namespace level variable depends on the value assigned to a different namespace level variable that might or not be initialized before. In your two examples there is no such dependency and there should not be any problem.

This, on the other hand, is prone to that type of error:

// header.h
extern const std::string foo;

// constant.cpp
const std::string foo( "foo" );

// main.cpp
#include "header.h"
const std::string foobar( foo+"bar" );
int main() {
   std::cout << foobar << std::endl;
}

There is no guarantee that foo will be initialized before foobar, even if both are constant. That means that the program behavior is undefined and it could well print "foobar", "bar" or die.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • I updated my use case in the original question. This is exactly the scenario I want to know about. – Hannah S. Jan 26 '11 at 16:08
  • I wouldn't dare comment on that. My first thought is that it should be fine (the constant is local to the translation unit, and is defined before the second constant), but I would if possible avoid that construct. The simplest way I know to be certain would be using a function with an static variable internally instead of the global var: `inline myclass& global_object() { myclass instance; return instance; }` The language guarantees that `instance` will be fully initialized in the first call to the function, so `std::string other( global_object().Get() );` will surely work... – David Rodríguez - dribeas Jan 26 '11 at 20:31
  • 2
    At any rate, I would *really* try to avoid it altogether and eliminate the namespace level variables with their initialization. Initialization of namespace level variables is not trivial, it happens in two passes, where all *globals* that are instantiated from constants get their values first, and then in a second pass everything that is dependent on a non-constant will be initialized according to the order of definition (within the same translation unit), in an undefined order when more than one translation unit is linked into a program. – David Rodríguez - dribeas Jan 26 '11 at 20:34
  • Makes me wonder how come languages such as Python and Java do not have this kinds of problems. – quant_dev Nov 16 '15 at 10:28
  • 1
    @quant_dev: Separate compilation is most probably the culprit. In Python you have to include and things are executed exactly as they are interpreted. You can only refer to something that has already been defined. In Java you build a source file against the generated .class, and again this provides a clear hierarchy for the object files. In C or C++ you build separate translation units against *declarations*, the linker does not know how to order those. Not to mention that there might not be an order, you can (even if you shouldn't) have cyclic dependencies between TUs – David Rodríguez - dribeas Nov 16 '15 at 23:04
  • And through all this years of developing C++ nobody thought about the way of fixing this? – quant_dev Nov 17 '15 at 22:27
  • @quant_dev: But it is solved, you only need to pick a different language. No, really, this is the result of a design decision in the language, you cannot fix this and maintain backwards compatibility. – David Rodríguez - dribeas Nov 18 '15 at 14:53
  • Why not? You could add a new feature which would allow people to write new software which would not have this problem, and still compile the old code. Anyway, C++ is not fully backwards compatible... – quant_dev Nov 18 '15 at 19:39
  • 1
    @quant_dev: that's easy to say... how? Modules, I believe, will help in this direction, but you have to build in a completely different mode that inhibits circular dependencies (i.e. header vs. implementation files) and have a mechanism to track what depends on what else. Modules are in this line, but they won't block the issue and you will have to live with it for backwards compatibility. C++ is almost *fully* backwards compatible, that is one of the core criteria in the standard committee. Every breaking change is carefully weighted: how many people will be affected and what the fix is – David Rodríguez - dribeas Nov 18 '15 at 23:26
  • ... removing the header/implementation file approach is something that just won't happen. – David Rodríguez - dribeas Nov 18 '15 at 23:26
2

Static initialization fiasco refers to static variables that depend on each other. Merely defining some static const variables will not be a source of problems.

Puppy
  • 144,682
  • 38
  • 256
  • 465