1

I have a static unordered_map in my class C. I experience difference in behaviour if I put my class definition and declaration in different files from the file containing function main.

The thing is that I observed that if the class C is in the same compilation unit as function main, all is well, I see only once the text "new string created: c". However if I split my code into three files (see the listing below), I see "new string created: c" twice which means that my static unordered_map is wiped right before entering main.

My question would be: why does this happen? (The difference only happens when compiling with Apple LLVM compiler 4.1. I have tested it with g++4.7 -std=c++11 and the split code works out just fine.)
Thanks in advance for any ideas!

// would go to My_header.h

#include <unordered_map>
#include <string>
#include <iostream>

using namespace std;    

class C{
public:
  C(const string & s);
private:
  static unordered_map<string, string*> m;
  string *name;
};

// would go to My_code.cpp    
// (when separated, add #include "My_header.h")

unordered_map<string, string*> C::m;

C::C(const string & s):
name(NULL)
{
  string*& rs = m[s];
  if(rs)
  {
    name = rs;
  }
  else
  {
    cout<<"new string created: "<<s<<endl;
    rs = name = new string(s);
  }
}

// would go to main.cpp
// (when separated, add #include "My_header.h")

C c("c");

int main(int argc, const char * argv[])
{
  cout << "main" << endl;
  C c1("c");
}
Barney Szabolcs
  • 11,846
  • 12
  • 66
  • 91
  • So you're showing code that works, and asking why some other code doesn't work? – Pete Becker Dec 15 '12 at 15:17
  • You can't depend on which translation unit is initialized first: http://stackoverflow.com/q/3746238/951890 – Vaughn Cato Dec 15 '12 at 15:18
  • @PeterBecker: No, Im showing the code. If you put this code into the same file, as it is, it works fine. If you put this code into three separate files, it does not work fine. – Barney Szabolcs Dec 15 '12 at 15:21
  • @VaughnCato still I do, with XCode for some reason I don't understand. – Barney Szabolcs Dec 15 '12 at 15:21
  • It appears that with LLVM, the globals in main.cpp are initialized before My_code.cpp, so your string gets added to an uninitialized map, then the map is initialized causing it to be cleared. With the other compilers, the map is initialized first, and then the string is added to it. – Vaughn Cato Dec 15 '12 at 15:24
  • Thanks, that agrees with what Dietmar told me. But how can I enforce the initialisation of my variable m? – Barney Szabolcs Dec 15 '12 at 15:26
  • @BarnabasSzabolcs - if I cut up that file in the places you've indicated I get code that won't compile, so clearly the code that doesn't work is different. – Pete Becker Dec 15 '12 at 15:34
  • Don't worry (of course you'd need the appropriate includes that was not the point here), Dietmar and Vaughn solved the problem already... – Barney Szabolcs Dec 15 '12 at 15:38
  • @BarnabasSzabolcs - I'm not worried. If you prefer guesses to engineering, so be it. – Pete Becker Dec 15 '12 at 16:39
  • @PeteBecker covered your request with an update. I would not use nomally "using namespace" in headers, but this time it will do. – Barney Szabolcs Dec 15 '12 at 17:53

2 Answers2

6

The order of initialization of global objects is defined only within one translation unit. Between different translation the order isn't guaranteed. Thus, you probably see behavior resulting from the std::unordered_map being accessed before it is constructed.

The way to avoid these problems is to not use global objects, of course. If you realky need to use a global object it is best to wrap the object by a function. This way it is guaranteed that the object is constructed the first time it is accessed. With C++ 2011 the construction is even thread-safe:

T& global() {
    static T rc;
    return rc;
}
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • Thanks, but how do I do a private static variable of a class into a function like this? Should I call a static function like this from the class constructor? – Barney Szabolcs Dec 15 '12 at 15:23
  • ??? You just use a `static` and `private` function which is implemented like the one above. Of course, if the function only lives in the translation unit where your class is implemented you might want to not declare it in the class at all and make it `static` so it isn't visible from any other translation unit. – Dietmar Kühl Dec 15 '12 at 15:33
  • yes, indeed, that is why I wrote I figured out. It did ring the bell finally... :) there is even an Effectice C++ item on this isn't it? – Barney Szabolcs Dec 15 '12 at 15:36
  • yes it was. Item 47: Ensure that non-local static objects are initialized before they're used. I was just foolish enough to think that private static variables count as local. – Barney Szabolcs Dec 15 '12 at 15:42
0

Thanks, guys! Following Dietmar's advice, I did this:

class C{
//...
private:
  static unordered_map<string, string*>& m();
};

unordered_map<string, string*>& C::m()
{
  static unordered_map<string, string*> m;
  return m;
}

and then I kept referring to m(). It is strange that it did not happen before. I guess I got lucky. But then, this should be a case for a warning message, shouldn't it?


To avoid mistakes like this I will use the following macros to declare and define static variables:

/// Use this macro in classes to declare static variables
#define DECLARE_STATIC(type, name) static type& name();

/// Use this macro in definition files to define static variables
#define DEFINE_STATIC(type, name) type& name(){static type local; return local;}

Usage in this case:

class C{
//...
private:
  DECLARE_STATIC(unordered_map<string, string*>, m);
}
DEFINE_STATIC(unordered_map<string, string*>, m)
Barney Szabolcs
  • 11,846
  • 12
  • 66
  • 91
  • Note that you need to return the object reference (`T&`) because you'd otherwise access a temporary object instead of the object held by the function! The declaration in your answer doesn't use a reference although your implementation does... – Dietmar Kühl Dec 15 '12 at 15:36
  • Thanks, yes, that was a typo in the declaration. – Barney Szabolcs Dec 15 '12 at 15:39