Correctly initializing static array of objects without encountering initialization order fiasco using object references

2.3k views Asked by At

I'm trying to implement a lightweight map by simply using an array of elements whose members are a key and a value. The map contents are known at compile-time so I think of using a fixed-size array as follows:

#include "stdafx.h"
#include <string>

// class Item is declared in file1.h. Definition could be in file1.cpp.
class Item
{
public:
    Item(const std::string name) : m_name(name) {}
    const std::string GetName() const { return m_name; }

private:
    const std::string m_name;
};

// The static consts are declared in file2.h which includes file1.h.
static const Item ITEM1 = std::string("Item1");
static const Item ITEM2 = std::string("Item2");
static const Item ITEM3 = std::string("Item3");
static const Item ITEM4 = std::string("Item4");

// ItemMapEntry and ItemMapUser is defined in file3.h...
struct ItemMapEntry
{
    const Item& key;
    const Item& value;
};

class ItemMapUser
{
public:
    void Run();

private:
    static const ItemMapEntry map[];
};

// and declared in file3.cpp which includes file2.h.
const ItemMapEntry ItemMapUser::map[] =
{
    { ITEM1, ITEM2 },
    { ITEM3, ITEM4 }
};

void ItemMapUser::Run()
{
    for (int i = 0; i < (sizeof(map) / sizeof(map[0])); i++)
    {
        printf("%s        %s\n", map[i].key.GetName().c_str(), map[i].value.GetName().c_str());
    }
}

// main.cpp includes file3.h.
int main()
{
    ItemMapUser itemMapUser;
    itemMapUser.Run();
}

Now to my question: The code snippet works as intended but I somehow have the feeling that I'm relying on the initialization order to have the ITEM1 to ITEM4 having their content initialized before using them in the ItemMapUser::map. I searched through the many questions referring to this topic (especially those with the static-order-fiasco tag) but couldn't find any one related to the use of arrays.

  • May I encounter the initialization order fiasco?
  • If no, what prevents it from happening here?
  • Does it matter that I'm using an array? How would it look like if I tried to initialize a simple variable using const Item anotherItem = ITEM1; for example?
3

There are 3 answers

2
James Kanze On BEST ANSWER

Do you use ItemMapUser::map in code which can be called from the constructor of a static object? There's no problem initializing the references with unconstructed objects, but there will be if you use them before the object is constructed.

Re your questions:

  1. Not unless you actually use the objects the references designate in the constructor of a static object.

  2. Basically, the fact that these are references, and you can safely initialize a referene with an unconstructed object. (There are certain restrictions when inheritance is involved, but they don't seem to be relevant here.)

  3. It has nothing to do with whether you're initializing an object or an array. If you're initialization an object (member of an array or not) rather than a reference, and calling the copy constructor, the object being copied had better be constructed. Which you can only guarantee if it is defined in the same translation unit.

1
Neutrino On

I would say yes you may encounter the static initialization order fiasco.

The reason you have not encountered it yet is that the application has not yet got complicated enough to create a circular static initialization.

I don't think using an array makes any difference.

At any rate it isn't a risk worth taking. I would start with the following pattern and evolve it as required, i.e. creating a static class or whatever best suits your purpose.

#include <iostream>
#include <string>
#include <map>
using namespace std;


// Define safely in any module and use safely from any module.
// With C++11 use of an initializer list could eliminate the init statement.
// If you want to define the constants separately from the map use of constexpr
// should preclude any initialization order issues.
static const map<string, string>& Map()
{
   static map<string, string> map;
   static bool init = false;

   if(!init)
   {
      map["Item1"] = "Item2";
      map["Item3"] = "Item4";
      init = true;
   }

   return map;
}


int main()
{
   cout << "Hello world!";
}
1
Pete Becker On

Maybe the question was simplified in the course of asking, but there's an awful lot of thrashing going on here for something that's basically much simpler.

struct entry {
    const char *key;
    const char *value;
};

entry data_map[] = {
    "Item1", "Item2",
    "Item3", "Item4",
    0,       0
};

for (entry *current = data_map; current->key != 0; ++current)
    printf("%s %s\n", current->key, current->value);