一个函数中多个静态变量的静态初始化
static initialization of multiple static variables in one function
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所写的那样逐行执行的。因此:
- std::map foo::sqlenumToInt=initEnumToInt()
- 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;
}
- 使用g++静态初始化带有命名标签的嵌套C++结构
- 静态初始化的反义词是什么?
- 在静态初始化期间运行代码
- 结构数组的嵌入式C++静态初始化
- 初始化与类类型相同的静态成员(静态初始化顺序问题)
- 使用 constexpr 和 std::array 进行静态初始化来替换动态初始化的 std::vector 的闰年
- MSVC 2017 在单个翻译单元中违反静态初始化顺序
- VC++6 线程安全静态初始化
- 具有必要副作用的静态初始化被优化掉了
- 未使用全球的静态初始化器
- w/ w/结构带char缓冲液的静态初始化[]
- 静态初始化的对象不能放在块中尝试
- 功能指针的静态初始化
- lambda(或函数)C++中的静态初始化
- C++静态初始化顺序:添加到映射中
- 复杂类型数组的静态初始化
- C++ - 在派生类中静态初始化受基类保护的成员变量
- 静态初始化插件工厂
- 静态初始化包含功能指针的对象的constexpr std ::数组
- 找出静态初始化是否结束