在一个函数中静态初始化多个静态变量

时间:2012-09-06 11:55:03

标签: c++ static initialization

嗨我有静态std :: map和一些值,静态迭代器是这样的默认元素,并立即初始化:

<。>文件中的

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

在.c文件中

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;
}

静态变量初始化的默认顺序是什么。将是默认的     的std ::地图:迭代() 或iterator到sqlenumToInt的第一个元素

6 个答案:

答案 0 :(得分:3)

在翻译单元中,很好地定义了静态变量的初始化顺序;静态变量按定义的顺序初始化。因此initEnumToIntfoo::defaultIt初始化之前运行。在您的代码中,这将导致未定义的行为,因为在initEnumToInt运行点,foo::defaultIt处于未初始化(但零初始化)状态;然后,您在零初始化对象上调用operator=,然后调用期望零或未初始化对象的构造函数。

答案 1 :(得分:2)

你编写它的方式是,你正在访问一个未初始化的元素,因为sqlenumToInt的初始化器首先被评估;这可能是未定义的行为(取决于迭代器类型的细节)。

如果您想要地图的正面,请在初始化程序中说出defaultIt = sqlenumToInt.begin(),然后将其从initEnumToInt()中删除。

(此外,即使你在函数中获得的迭代器也没有意义,因为一旦局部地图对象被破坏它就会失效。)

答案 2 :(得分:1)

文件范围变量按其定义的顺序初始化。在示例代码中,sqlenumToInt将首先被初始化,调用initEnumToInt,它将defaultIt设置为在函数调用结束时变为无效的迭代器值(它指向{{1} },被摧毁; test获得sqlenumToInt副本。然后test的显式初始化开始,存储默认构造的迭代器。

答案 3 :(得分:0)

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

此行为map<string, int>构建默认迭代器,因此您的defaultIt将只是此默认迭代器的副本。如果您想要第一个地图元素,则应使用sqlenumToInt.begin()初始化它。

对于初始化的顺序,在一个编译单元中,静态变量的初始化顺序与定义它们的顺序相同,但不同单元之间的顺序是未定义的。

答案 4 :(得分:0)

initEnumToInt()内的这一行有问题:

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

此代码有两个问题。第一个是因为在到达行之前没有构造迭代器,它会导致未定义的行为如果迭代器没有一个简单的构造函数(调用{{1}在未初始化的对象上 - 这在某些情况下不是问题,但代码不可移植。)

该行的第二个问题是您正在设置迭代器以引用 local 映射中的元素。在函数完成后对该迭代器的任何使用都将是未定义的行为。

请注意,在函数内部设置迭代器不会向代码添加任何值,因此您可以将setter放在一边。如果您想要的是引用该元素的迭代器,您可以执行多项操作:如果始终是第一个元素,则在其初始化程序中将其设置为operator=,如果您愿意引用地图中的特定元素(在初始化地点已知,将其设置为sqlenumToInt.begin()。如果要将其设置为仅在sqlenumToInt.find(element)函数内部已知的特定元素,则更改初始化的顺序,以便首先初始化迭代器并通过引用将其作为参数传递给initEnumToInt函数。 - 这不是必需的,作为公共静态变量,函数无论如何都可以访问它,但是传递它引用使得依赖性以及在代码中显式函数中修改它的事实。

答案 5 :(得分:0)

初始化是在@ecatmur写的时候逐行执行的。所以:

  1. std :: map foo :: sqlenumToInt = initEnumToInt();
  2. std :: map foo :: defaultIt = std :: map :: iterator();
  3. 为了解释它,我写了一个简单的例子:

    // 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;
    }
    

    输出是:

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

    因此,分配静态变量的内存,但稍后执行初始化,类似&#34; placement new&#34;机械。您可以在输出中看到,在init之后调用bar的c,但旧值为1(将由init方法设置)并且由于静态初始化而覆盖为2。

    因此,您可以通过更改顺序静态初始化轻松解决此问题。但是你有另一个问题由@Kerrek SB描述:

      

    (而且,即使你在函数中获得的迭代器也会   没有意义,因为它一旦局部地图就变得无效   对象被破坏。)

    纠正您案件的变体之一:

    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;
    }