2014-09-26 145 views
1

这里赛格故障代码,简化...访问单身般的静态成员

//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是静态的还是匿名的命名空间最初也会出现?

反正, 所以当运算符[]执行时,repo似乎是未初始化的。我添加了一个与repo具有相同签名的本地虚拟变量(repo_debug)。并且观察到它们的值用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

通常你只会问只有一个问题(如果他们是严格相关的话可能会好起来) – 2014-09-26 08:22:53

+3

我最初的看法:你的静态和全局正在比赛初始化。猜猜谁输了。 – WhozCraig 2014-09-26 08:26:12

+0

Logger :: log'在哪里实例化,以及所有这些'logger = ...'?相同的编译单元?不同的编译单元?在第二种情况下,您遇到了麻烦,初始化顺序未定义。如果你想以这种方式使用它,创建一些吸气剂(吸气剂将首先检查并创建它,可能使用原子)。或者在一个文件中实例化所有的记录器。 – firda 2014-09-26 08:32:53

回答

1

问题1:假定声明是在不同的编译单元中。编译单元之间的静态初始化顺序是实现定义的。所以,这是偶然的。我会说幸运的机会,因为另一种方式,你最初会认为它只是在后来发现它在另一个CPU /编译器/操作系统上崩溃。

问题2:由于匿名命名空间中的logger被初始化,导致segfault阻止静态初始化log

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

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

的缺点是动态分配的对象永远不会真正释放(单身人士将在该月底被释放程序无论如何,但可能是一个问题,如果你没有一个操作系统运行)

静态本地化的初始化的线程安全性由§6中的标准保证。7/4(C++ 11草案):

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

在早期的标准,并在Visual C++中,你可以通过确保log被称为是静态初始化(主程序可以生成任何线程之前,其发生)时调用至少有一个构造避免并发问题。

+0

这种方法是线程安全的吗?我指的是通过本地静态创建单例。 – firda 2014-09-26 08:40:37

+0

只要构造函数本身是线程安全的。 – user2079303 2014-09-26 08:47:50

+0

那么,这个本地静态内部使用了一些原子?当多个线程同时请求记录器时,它不能被多次创建? – firda 2014-09-26 08:54:03