静态方法中使用的c ++ static unorderd_map未初始化

时间:2016-09-29 14:00:06

标签: c++ stl

我有一些代码,其中调用静态方法,并且未初始化同一文件中的静态std::unordered_map。我理解两个编译单元之间的静态初始化是" undefined"关于这个主题有很多SO个问题;但是,当我使用std::vector时,问题不会发生。此外,代码可以执行,但我很困惑为什么这些特定的编译命令不起作用。 SO,我的问题是:

  1. 关于静态初始化和静态变量的动态初始化,还有另一个SO问题(我一直无法找到!)。这个错误是由于std::undored_map实际上是动态初始化吗?
  2. 有没有办法让这段代码按照我的预期初始化std::unordered_map?我实际上是在尝试创建静态库.lib.a。当我链接静态库时,它通常需要最后,因此发生错误。
  3. 这有什么变通方法吗?我想到的一个选择是同时创建std::vectorstd::unordered_map。在std::vector未初始化(通过std::unordered_map)时使用bool _map_is_initialized。通过调用迭代std::unordered_map中的值以生成std::vector的函数,将std::unordered_map的初始化更改为显式动态。
  4. 的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::Beforestatic std::vector<Thing*> Thing::After被初始化,但static std::unordered_map<std::string, Thing*> Thing::Things没有?

2 个答案:

答案 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源文件),但没有任何关于不同翻译单元中的订单初始化的信息。

当您将BeforeAfter向量添加到代码中时,您发现与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,这感觉很像内存泄漏。但即使它不是指针,也只能在程序关闭时进行解除初始化。因此,除非对象的析构函数执行一些重要的功能,例如将文件的内容刷新到磁盘,唯一重要的区别是使用指针避免了退出时崩溃的可能性。