简体   繁体   中英

static initialization of multiple static variables in one function

hi I have static std::map with some values and static iterator to default element like this and initialize both at once:

in .h file

class foo
{
    static std::map<std::string, int> sqlenumToInt;
    static std::map<std::string, int> initEnumToInt();
    static std::map<std::string, int>::iterator defaultIt;
};

in .c file

std::map<std::string, int> foo::sqlenumToInt = initEnumToInt();

std::map<std::string, int> foo::defaultIt = std::map<std::string, int>::iterator();

std::map<std::string, int> foo::initEnumToInt();
{
    std::map<std::string, int> test;
    defaultIt = test.insert(std::make_pair("a", 0)).first
    test["b"] = 2;
    test["c"] = 3;
    test["d"] = 4;
    return test;
}

What will be default order of initialization of static variables. Will be defaultIt only std::map::iterator() or iterator to first element of sqlenumToInt ??

Within a translation unit, initialization order of static variables is well defined; static variables are initialized in order of definition. So initEnumToInt runs before foo::defaultIt is initialized. In your code, this will result in undefined behaviour, since at the point initEnumToInt runs, foo::defaultIt is in an uninitialized (but zero-initialized) state; you are then calling operator= on a zero-initialized object, and later calling the constructor that expects a zero- or un-initialized object.

The way you've written it, you're accessing an uninitialized element, since the initalizer for sqlenumToInt is evaluated first; this may be undefined behaviour (depending on the details of the iterator type).

If you want the front of the map, say defaultIt = sqlenumToInt.begin() in the initializer and remove it from initEnumToInt() .

(Moreover, even the iterator that you obtained in your function would be meaningless, since it becomes invalid as soon as the local map object is destroyed.)

File-scope variables are initialized in the order of their definition. In the sample code, sqlenumToInt will be initialized first, calling initEnumToInt , which sets defaultIt to an iterator value that becomes invalid at the end of the function call (it points into test , which gets destroyed; sqlenumToInt gets a copy of test ). Then the explicit initialization of defaultIt kicks in, storing a default-constructed iterator.

std::map<std::string, int>::iterator();

this line construcs a default iterator for map<string, int> , so your defaultIt will be just a copy of this default iterator. If you want the first map element, you should initialize it with sqlenumToInt.begin() .

As for the order of initialization, within one compilation unit static variables are initialized in the same order you define them, but the order between different units is undefined.

This line inside initEnumToInt() is problematic:

defaultIt = test.insert(std::make_pair("a", 0)).first

There are two things wrong with this code. The first is that because the iterator has not been constructed before the line is reached, it causes undefined behavior if the iterator does not have a trivial constructor (calling operator= on an uninitialized object --this is not a problem in some cases, but the code will not be portable).

The second problem with that line is that you are setting the iterator to refer to an element in a local map. Any use of that iterator after the function completes would be undefined behavior.

Note that setting the iterator inside the function does not add any value at all to the code, so you can just leave the setter aside. If what you want is the iterator to refer to that element you can do multiple things: if it is the first element always, then set it to sqlenumToInt.begin() in its initializer, if you want to refer to a particular element in the map (known at the place of initialization, set it to sqlenumToInt.find(element) . If you want it set to a particular element that is only known inside the initEnumToInt function, then change the order of initialization so that the iterator is initialized first and pass it to the initEnumToInt function as an argument by reference. --This is not required, being a public static variable the function can access it anyway, but passing it by reference makes the dependency and the fact that it gets modified in the function explicit in the code.

Initialization are perform line by line as @ecatmur wrote. So:

  1. std::map foo::sqlenumToInt = initEnumToInt();
  2. std::map foo::defaultIt = std::map::iterator();

To explain it i've write simple example:

// foo.h
#pragma once

#include <iostream>

struct bar
{
    int value_;
    bar(int value)
    {
        std::cout << "bar()\n";
        set(value, true);
    }
    void set(int value, bool fromConstructor = false)
    {
        std::cout << ((fromConstructor) ? "Set from ctor" : "Set from non-ctor")
            << ", old value is: " << value_ << "\n";
        value_ = value;     
    }   
};

struct foo
{
    static bar bar_;

    static bool init()
    {
        std::cout << "init begin\n";
        bar_.set(1);
        std::cout << "init end\n";
        return true;
    }
};

// main.cpp
#include "foo.h"

bool b = foo::init();
bar foo::bar_ = bar(2);

int main()
{
    std::cout << foo::bar_.value_ << "\n";
    return 0;
}

And output is:

init begin
Set from non-ctor, old value is: 0
init end
bar()
Set from ctor, old value is: 1
2

So, memory for static variable are allocated but initialization are perform later, similar "placement new" mechanic. You can see in output that c'tor of bar are called after init, but old value is 1 (which will be set by init method) and overwrite to 2 due static initialization.

So, you can easy resolve that by changing order static initialization. But you have another problem described by @Kerrek SB:

(Moreover, even the iterator that you obtained in your function would be meaningless, since it becomes invalid as soon as the local map object is destroyed.)

One of variant to correct your case:

class foo
{
    typedef std::map<std::string, int> Map;

    static bool initEnumToInt();

    static Map sqlenumToInt;
    static Map::iterator defaultIt;
    static bool inited;
};

foo::Map foo::sqlenumToInt;
foo::Map::iterator defaultIt = foo::Map::iterator();
bool foo::sqlenumToInt = initEnumToInt();

bool foo::initEnumToInt();
{
    defaultIt = sqlenumToInt.insert(std::make_pair("a", 0)).first;
    sqlenumToInt["b"] = 2;
    sqlenumToInt["c"] = 3;
    sqlenumToInt["d"] = 4;
    return true;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM