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

static initialization of multiple static variables in one function

本文关键字:静态 初始化 变量 函数 一个      更新时间:2023-10-16

hi我有静态std::映射一些值和静态迭代器到这样的默认元素,并同时初始化这两个元素:

in.h文件

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

静态变量初始化的默认顺序是什么。将是defaultIt onlystd::map::迭代器()或迭代器到sqlenumToInt的第一个元素??

在一个翻译单元中,静态变量的初始化顺序是明确定义的;静态变量按照定义的顺序进行初始化。因此initEnumToInt在初始化foo::defaultIt之前运行。在您的代码中,这将导致未定义的行为,因为在initEnumToInt运行时,foo::defaultIt处于未初始化(但零初始化)状态;然后对零初始化的对象调用operator=,然后调用需要零初始化或未初始化对象的构造函数。

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

如果您想要映射的前面,请在初始值设定项中说defaultIt = sqlenumToInt.begin(),并将其从initEnumToInt()中删除。

(此外,即使是您在函数中获得的迭代器也将毫无意义,因为一旦本地映射对象被破坏,它就会变得无效。)

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

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

这一行构造了map<string, int>默认迭代器,因此您的defaultIt将只是该默认迭代器的副本。如果您想要第一个map元素,则应该使用sqlenumToInt.begin()对其进行初始化。

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

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

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

这个代码有两个错误。第一种情况是,因为在到达行之前,迭代器还没有构造好,所以如果迭代器没有一个平凡的构造函数(在未初始化的对象上调用operator=——在某些情况下这不是问题,但代码不可移植),就会导致未定义的行为。

该行的第二个问题是,您将迭代器设置为引用本地映射中的元素。在函数完成后使用该迭代器将是未定义的行为。

请注意,在函数内部设置迭代器根本不会给代码添加任何值,因此可以将setter放在一边。如果你想要迭代器引用该元素,你可以做很多事情:如果它总是第一个元素,那么在其初始值设定项中将其设置为sqlenumToInt.begin(),如果要引用地图中的特定元素(在初始化时已知,请将其设置为sqlenumToInt.find(element)。如果您希望将它设置为仅在initEnumToInt函数内部已知的特定元素,请更改初始化顺序,使迭代器首先初始化,并通过引用将其作为参数传递给initEnumToInt函数。--这不是必需的,作为公共静态变量,函数无论如何都可以访问它,但g it by reference使依赖关系和它在函数中被修改的事实在代码中显式。

初始化是按照@ecatmur所写的那样逐行执行的。因此:

  1. std::map foo::sqlenumToInt=initEnumToInt()
  2. std::map foo::defaultIt=std::map::迭代器()

为了解释它,我写了一个简单的例子:

// 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 beginn";
        bar_.set(1);
        std::cout << "init endn";
        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

因此,静态变量的内存被分配,但初始化稍后执行,类似于"放置新"机制。您可以在输出中看到,bar的c'tor在init之后被调用,但旧值是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;
}