这是代码,简化......
//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。非常感谢您提出解决此问题的建议。 感谢
答案 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
来避免并发问题。