START
自从C++11 保证了静态局部变量的线程安全性之后,单例在C++11 里就简单得可怕:
class Singleton {
public:
Singleton(const Singleton&) = delete;
Singleton& operator = (const Singleton&) = delete;
static Singleton& instance(){
static Singleton instance; // #1
return instance;
}
private:
Singleton() = default;
~Singleton() = default;
};
代码#1 是关键。所以C++11 之后单例经常被使用,一般情况下用起来确实很爽,但是也会引起一些问题。
第一个问题:跨动态库的问题。
如果两个so 都引用一个单例,一般情况下会在两个so 里各产生一个单例,虽然gcc下可以通过编译选项让单例跨so的时候仍然只会有一个实例,但是,对于clang编译器是无解的。解决办法有两个,各自的so里使用自己的单例,二者不相关的时候是可以的;还有一个办法是让其它so的单例依赖某个so的单例,自己建立依赖关系,保证不同的so 只依赖一个单例,就是比较麻烦。
第二个问题:单例生命周期依赖的问题。
如果有两个单例A和B,其中B 依赖了A,在B的析构函数里需要访问A,这时候可能会有这样一个场景,先创建B的单例,再创建A的实例,然后B析构的时候再访问A单例,这时候就会出现一个访问错误:访问了已经释放的单例A。这是一个典型的单例依赖问题,最近社区报了一个yalantinglibs 的一个issue就是这样的。
// Static de-initialization order problem
// File1.h
class A {
....
void doSomething() {
...
}
}
extern A aObj;
//File1.cpp
static A aObj;
// File2.cpp
class B {
B() {}
~B() {
aObj.doSomething(); // Not okay! aObj may have already been destructed!
}
....
}
static B bObj;
因为C++里的静态对象的初始化规则是这样的:"The order in which static objects are initialized across different translation units is undefined or ambiguous."
简言之不能保证静态变量的初始化顺序。那应该怎么解决这个单例生命周期依赖的问题呢?
也很简单,利用另外一条规则:Construct on first use idiom。如果你能控制第一次创建单例的顺序,那么就能解决这个问题。比如你先创建A单例,再创建B单例,A比B先创建,那么A 会比B 后析构,这时候在B的析构函数里访问单例A 是安全的。
// Static initialization order problem
// File1.h
class A {
....
void doSomething() {
...
}
};
A& aObj();
//File1.cpp
A& aObj() {
static A *aObj = new A();
return *aObj;
}
// File2.cpp
class B {
B() {
/*
* Okay since calling aObj() gaurantees that
* static A *aObj = new A(); ran
*/
aObj().doSomething();
}
~B() {
aObj().doSomething(); // Okay! it's safe!
}
};
static B bObj;
很简单的一个改动,在B的构造函数里先创建A 单例,这样就能保证A比B先创建,B 析构的时候A 还在,访问A是安全的,从而解决了单例生命周期依赖的问题。