我有一些代码,其中调用静态方法,并且未初始化同一文件中的静态std::unordered_map
。我理解两个编译单元之间的静态初始化是" undefined"关于这个主题有很多SO
个问题;但是,当我使用std::vector
时,问题不会发生。此外,代码可以执行,但我很困惑为什么这些特定的编译命令不起作用。 SO
,我的问题是:
SO
问题(我一直无法找到!)。这个错误是由于std::undored_map
实际上是动态初始化吗?std::unordered_map
?我实际上是在尝试创建静态库.lib
或.a
。当我链接静态库时,它通常需要最后,因此发生错误。std::vector
和std::unordered_map
。在std::vector
未初始化(通过std::unordered_map
)时使用bool _map_is_initialized
。通过调用迭代std::unordered_map
中的值以生成std::vector
的函数,将std::unordered_map
的初始化更改为显式动态。的Linux
g++ -std=c++1y -g -c thing.cpp
g++ -std=c++1y -g -c main.cpp
g++ -g main.o thing.o -o main
./main
这会导致Floating point exception (core dumped)
错误。通过gdb
,我能够找出hashtable_policy.h
trys __num % __den;
__den==0
。同样使用gdb
,似乎Thing::Things
未初始化。
(gdb) break thing.cpp:12
(gdb) run
(gdb) print Thing::Things
No symbol "Things" in specified context.
(gdb) print thing
$1 = (Thing *) 0x618c20
窗
cl /EHsc /Zi /c main.cpp
cl /EHsc /Zi /c thing.cpp
link /debug main.obj thing.obj
main
在我的实际代码中,这导致了非常明显的分段错误;但是,此示例只会打开一个弹出窗口,指出应用程序失败。 ......我没有做过更好的诊断。
代码
thing.cpp
#include<iostream> #include "thing.hpp" std::vector<Thing*> Before; // EDIT: added std::unordered_map<std::string, Thing*> Thing::Things; std::vector<Thing*> After; // EDIT: added Thing::Thing(std::string name) : name(name) { } bool Thing::Register(Thing *thing) { std::cout << "no worries, vectors initialized..." << std::endl; Thing::Before.push_back(thing); // EDIT: added Thing::After.push_back(thing); // EDIT: added std::cout << "added to vectors, about to fail..." << std::endl; Thing::Things[thing->name] = thing; return true; }
thing.hpp
#pragma once #include <string> #include <unordered_map> class Thing { public: static std::vector<Thing*> Before; // EDIT: added static std::unordered_map<std::string, Thing*> Things; static std::vector<Thing*> After; // EDIT: added static bool Register(Thing* thing); std::string name; Thing(std::string name); }; #define ADD_THING(thing_name) \ static bool thing_name## _is_defined = Thing::Register(new Thing( #thing_name ));
main.cpp
#include "thing.hpp" #include <iostream> ADD_THING(obligatory); ADD_THING(foo); ADD_THING(bar); int main(int argc, char* argv[]) { std::cout << "before loop" << std::endl; for (auto thing : Thing::Things) { std::cout << "thing.name: " << thing.first << std::endl; } return 0; }
修改
如果保证给定编译单元中的顺序,为什么static std::vector<Thing*> Thing::Before
和static std::vector<Thing*> Thing::After
被初始化,但static std::unordered_map<std::string, Thing*> Thing::Things
没有?
答案 0 :(得分:3)
如评论中所述,未定义静态初始化顺序。谁知道矢量和地图之间的区别。也许你的编译器首先在名字中用偶数个字符初始化类。
如果您正在运行c++11
或更高版本,则保证函数本地项的静态初始化是线程安全的。它们将在控件第一次通过声明声明时初始化。
// Header
class Thing {
public:
static std::unordered_map<std::string, Thing*>& Things();
static bool Register(Thing* thing);
// CPP
std::unordered_map<std::string, Thing*>& Thing::Things()
{
static std::unordered_map<std::string, Thing*> things;
return things;
}
这将在您第一次请求Things
时初始化,并避免静态初始化的所有潜在随机性。
答案 1 :(得分:2)
静态初始化很棘手。正如this answer所述,该标准对单个翻译单元中的初始化顺序提供了一些保证。 (通常是一个.cpp源文件),但没有任何关于不同翻译单元中的订单初始化的信息。
当您将Before
和After
向量添加到代码中时,您发现与ordered_map::operator[]
的调用不同,对vector::push_back()
的调用并未使进程崩溃,得出的结论是,在一个翻译单元内,对象被无序初始化,这与标准的保证相反。那里有一个隐藏的假设,即由于push_back()
没有导致崩溃,因此必须初始化向量。结果并非如此:对未初始化对象的方法调用几乎肯定会在某处破坏内存,但不一定会导致崩溃。检查构造函数是否被调用的更好方法是在调试器中运行代码,并在包含对象的行上设置断点。定义,例如std::vector<Thing*> Before
中的thing.cpp
。这将显示初始化将按标准中的预测进行。
如here所述,避免&#34; fiasco&#34;的最佳选择是&#34;在第一次使用&#34;时构建。对于您的示例代码,这将涉及更改Thing::Things
的任何直接使用,例如此行:
Thing::Things[thing->name] = thing;
一个方法,比如Thing::GetThings()
,它初始化对象并返回对它的引用。 lcs' answer提供了一个示例,但要注意:虽然它解决了静态初始化问题,但使用范围内的静态对象可能会引入更有害的问题:由于static deinitialization order导致程序退出时崩溃。因此,首选使用new
关键字分配对象:
std::unordered_map<std::string, Thing*>& Thing::GetThings()
{
static std::unordered_map<std::string, Thing*>* pThings =
new std::unordered_map<std::string, Thing*>();
return *pThings;
}
那个例子当然永远不会是delete
,这感觉很像内存泄漏。但即使它不是指针,也只能在程序关闭时进行解除初始化。因此,除非对象的析构函数执行一些重要的功能,例如将文件的内容刷新到磁盘,唯一重要的区别是使用指针避免了退出时崩溃的可能性。