用于访问类似单身的静态成员的seg fault

时间:2014-09-26 08:19:09

标签: c++

这是代码,简化......

//A class used in the next class, Nothing much to worry about
class BasicLogger{
//...
};

以下是我的主要课程。它有两个你需要查看的成员:一个自己类型的静态成员(称为log)。 并且,一个容器(称为repo)用于保存上述类的对象。可以使用repo重载访问operator[]项:

class Logger {
protected:
    //  repository of profilers. each profiler is distinguished by a file name!
    std::map<const std::string, boost::shared_ptr<BasicLogger> > repo; 
public:
    Logger(){} //breakpoints never reach here. why?
    //universal singleton-like access to this class
    static Logger log;
    //returns a member stored in the above 'repo' 
    virtual BasicLogger & operator[](const std::string &key);
};

问题来自这种方法:

BasicLogger & Logger::operator[](const std::string &key)
{
    std::map<std::string, boost::shared_ptr<BasicLogger> >::iterator it = repo.find(key);
    if(it == repo.end()){
        std::cout << "creating a new Logger for " << key << std::endl;
        boost::shared_ptr<BasicLogger> t(new LogEngine(key));
        std::map<const std::string, boost::shared_ptr<BasicLogger> > repo_debug;//just for debug
        repo_debug.insert(std::make_pair(key,t));//ok
        repo.insert(std::make_pair(key,t));//seg fault
        return *t;
    }
    return *it->second;
}

和最后一条信息:在整个项目中,repo容器中的项目如下所示。

namespace{
BasicLogger & logger = Logger::log["path_set"];
}

问题:

问题在于,在程序开始之前,控件直接进入BasicLogger & logger = Logger::log["path_set"];

Q1:为什么控件首先会在这里?只是因为log是静态的还是最初也参加了匿名命名空间?

反正, 因此,当执行operator []时,repo似乎未初始化。我添加了一个与repo_debug具有相同签名的本地虚拟变量(repo)。并使用gdb观察它们的值:

//local repo_debug
    Details:{... _M_header = {... _M_parent = 0x0, _M_left = 0x7fffffffdc08, _M_right = 0x7fffffffdc08}, _M_node_count = 0}}}
//main 'repo'
    Details:{..._M_parent = 0x0, _M_left = 0x0, _M_right = 0x0}, _M_node_count = 0}}}

Q2。为什么repo未初始化?基本上,为什么不调用Logger的构造函数?

Q3。非常感谢您提出解决此问题的建议。 感谢

1 个答案:

答案 0 :(得分:1)

Q1:据推测,声明是在单独的编译单元中。编译单元之间的静态初始化顺序是实现定义的。所以,这是偶然的。我会说幸运的机会,因为你最初认为它的另一种方式只是发现它在以后的另一个cpu / compiler / os上打破了。

Q2:因为匿名命名空间中的logger已初始化,导致出现段错误,导致静态log无法初始化。

Q3。您可以通过避免设计中的单例来避免此问题。但是如果你想要单身,一种避免静态初始化顺序惨败的方法就是构建第一次使用成语:

Logger& Logger::log() {
   static Logger* log = new Logger();
   return *log;
}

缺点是动态分配的对象永远不会被释放(无论如何都会在程序结束时释放单例,但如果你在没有操作系统的情况下运行则可能会出现问题)

静态局部的初始化的线程安全性由§6.7/ 4(c ++ 11 draft)中的标准保证:

  

......否则这样的变量就是   初始化第一次控制通过其声明;这样的变量被认为是初始化的   完成初始化。如果初始化通过抛出异常退出,则初始化   未完成,因此下次控制进入声明时将再次尝试。 如果控制进入   在初始化变量的同时声明,并发执行应该等待   完成初始化。

在早期标准和visual c ++中,您可以通过确保在静态初始化期间调用的至少一个构造函数(在主程序可以生成任何线程之前发生)中调用log来避免并发问题。