说在前面
首先要明确以下几点:
- C++中全局静态变量和静态成员变量(以下简称静态变量,其他类型的变量会单独说明)的初始化顺序由编译器决定,不同编译器下初始化顺序基本不一样,但是在同一个编译器下顺序却是一样,但是我们很难掌握其顺序(g++中好像有明确的方法指定顺序),所以静态变量在初始化时不要相互依赖。
- 正常情况下静态变量在进入main函数之前即会完成初始化(有例外)。
- 局部静态变量的初始化时机十分明确,一定是第一次调用该函数时初始化。
- 只要某一个模块(exe程序,dll库)被加载,那么该模块中的静态变量一定会被初始化,但是,并不一定是在main函数之前。如果模块使用的是头文件及链接lib的方式,那么它会在main函数之前加载;如果模块是运行时动态加载(例如QT中的插件),那么该模块中的静态变量将会在第一次加载时初始化(目前在windows上测试是这样,linux上暂未测试)。
- C++11以后的局部静态变量初始化时编译器必须保证其线程安全性。
- 静态变量是在模块卸载的时候析构的,如果是主程序或者直接链接到主程序的库,那么就是在出了main函数之后。
- 如果链接了动态库,那么动态库中的静态变量初始化先于主程序中的静态变量(关于这一点我没有找到明确的文章来说明,并且只在windows上测试过)。
正文
明确以上几点后,就可以使用这些特性来实现相应的功能了
局部静态变量实现单例模式
C++11以前写单例模式,如果要保证线程安全性的话要么用饿汉,如果懒汉的话一般就会使用双重锁。但是C++11以后就可以直接使用局部静态变量来满足懒汉和线程安全性两个特性:
Singleton *Singleton::instance()
{
static Singleton ins;
return &ins;
}
但是这种方法在QT里面使用的时候有一个致命的缺陷。由于静态变量的析构是在出了main函数之后的,也就是说这个单例对象在析构的时候QApplication已经完全析构了,如果你这个单例的析构函数里面某一个功能依赖信号槽的话,只有直接连接的方式能够被接收到。
虽然这种情况很少见,但是如果遇到了需要酌情处理一下。
局部静态变量的初始化顺序来实现静态变量之间的相互依赖
上面有说不要让静态变量在初始化时相互依赖,因为他们的初始化顺序不明确,有可能存在使用某一个静态变量时该变量还未被初始化的情况。但是局部静态变量是一个例外,因为它一定是在第一次调用该函数时才会初始化。
QT里的Q_GLOBAL_STATIC宏就是利用局部静态变量这一特性来实现的,展开这个宏可以看到下面的代码:
#define Q_GLOBAL_STATIC_INTERNAL(ARGS) \
Q_GLOBAL_STATIC_INTERNAL_DECORATION Type *innerFunction() \
{ \
struct HolderBase { \
~HolderBase() noexcept \
{ if (guard.loadRelaxed() == QtGlobalStatic::Initialized) \
guard.storeRelaxed(QtGlobalStatic::Destroyed); } \
}; \
static struct Holder : public HolderBase { \
Type value; \
Holder() \
noexcept(noexcept(Type ARGS)) \
: value ARGS \
{ guard.storeRelaxed(QtGlobalStatic::Initialized); } \
} holder; \
return &holder.value; \
}
template <typename T, T *(&innerFunction)(), QBasicAtomicInt &guard>
struct QGlobalStatic
{
typedef T Type;
bool isDestroyed() const { return guard.loadRelaxed() <= QtGlobalStatic::Destroyed; }
bool exists() const { return guard.loadRelaxed() == QtGlobalStatic::Initialized; }
operator Type *() { if (isDestroyed()) return nullptr; return innerFunction(); }
Type *operator()() { if (isDestroyed()) return nullptr; return innerFunction(); }
Type *operator->()
{
Q_ASSERT_X(!isDestroyed(), "Q_GLOBAL_STATIC", "The global static was used after being destroyed");
return innerFunction();
}
Type &operator*()
{
Q_ASSERT_X(!isDestroyed(), "Q_GLOBAL_STATIC", "The global static was used after being destroyed");
return *innerFunction();
}
};
#define Q_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ARGS) \
namespace { namespace Q_QGS_ ## NAME { \
typedef TYPE Type; \
QBasicAtomicInt guard = Q_BASIC_ATOMIC_INITIALIZER(QtGlobalStatic::Uninitialized); \
Q_GLOBAL_STATIC_INTERNAL(ARGS) \
} } \
static QGlobalStatic<TYPE, \
Q_QGS_ ## NAME::innerFunction, \
Q_QGS_ ## NAME::guard> NAME;
#define Q_GLOBAL_STATIC(TYPE, NAME) \
Q_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ())
关于这个宏如何使用可以查一下QT的文档,这里不展开说。我们可以看到的是,我们使用这个变量的时候真正调用的其实是innerFunction这个函数,而这个函数里面就是用了一个局部静态对象,那我们在其他静态变量初始化时向这个对象插入数据,或者从中读取数据一定是安全的,因为它会在第一次调用的时候被初始化,无论现在处于什么时机。
但是这个宏其实还是有一个问题,我们可以看到Q_GLOBAL_STATIC_WITH_ARGS宏下面有一个全局变量guard,这个变量的初始化时机是未知的,也就是说可能在调用innerFunction函数时这个变量还未被初始化,调用完之后才被初始化,那这个变量就有可能会是一个意料之外的值。不过非常庆幸的是这种情况只会对exists这个函数有影响,并不会真正影响innerFunction所包裹的那个局部静态对象,而且如果使用Q_GLOBAL_STATIC这个宏的代码位于动态库中,而又只是从库外调用这个宏所声明的对象的话,guard早就已经初始化完成了。
最后再插一句,QT实现这个宏可能最开始的初衷只是想实现懒加载,或者不调用就不加载的效果。
静态变量初始化时实现额外功能
- 静态对象的构造函数中调用其他函数提前完成某些功能,或者在外部不感知的情况完成某些功能。
如果你翻一翻qrc文件编译出来的cpp文件应该能找到下面的代码:
namespace {
struct initializer {
initializer() { QT_RCC_MANGLE_NAMESPACE(qInitResources_resource)(); }
~initializer() { QT_RCC_MANGLE_NAMESPACE(qCleanupResources_resource)(); }
} dummy;
}
当然这里用的不是静态变量,而是一个普通全局变量,但是它也会自动初始化,然后构造函数里调用一个已经实现好的其它函数,就能在外部不感知的情况下完成某些功能。比如这里我们可以调用qAddPreRoutine或者qAddPostRoutine向QT注册一些方法,QT里面有一个宏Q_COREAPP_STARTUP_FUNCTION就是这么实现的。
2. C++11之后有了lambda表达式,也可以实现相应的功能
static const int __Static_Code_Block__ = []() -> int {
...
return 0;
}();
后面的想到了再补吧…