如果我在不同的编译单元中有两个静态变量,则不会定义它们的初始化顺序。这一课很好学。
我遇到的问题:在初始化第一个静态变量时,已经分配了所有静态变量。换句话说:
static A global_a; // in compilation unit 1
static B global_b; // in compilation unit 2
struct A {
A() { b_ptr = &global_b; }
B *b_ptr;
void f() { b_ptr->do_something(); }
}
int main() {
global_a.f();
}
b_ptr是否指向一个有效的内存块,其中B是在执行main函数时分配和初始化的?在所有平台上?
更长的故事:
编译单元1是Qt库。 另一个是我的申请。我有几个QObject派生类,我需要能够通过类名字符串实例化。为此我想出了一个模板化的工厂类:
class AbstractFactory {
public:
virtual QObject *create() = 0;
static QMap<const QMetaObject *, AbstractFactory *> m_Map;
}
QMap<const QMetaObject *, AbstractFactory *> AbstractFactory::m_Map; //in .cpp
template <class T>
class ConcreteFactory: public AbstractFactory {
public:
ConcreteFactory() { AbstractFactory::m_Map[&T::staticMetaObject] = this; }
QObject *create() { return new T(); }
}
#define FACTORY(TYPE) static ConcreteFactory < TYPE > m_Factory;
然后我在每个QObject子类定义上添加这个宏:
class Subclass : public QObject {
Q_OBJECT;
FACTORY(Subclass);
}
最后,我可以通过类型名称实例化一个类:
QObject *create(const QString &type) {
foreach (const QMetaObect *meta, AbstractFactory::m_Map.keys() {
if (meta->className() == type) {
return AbstractFactory::m_Map[meta]->create();
}
}
return 0;
}
因此,类从Qt库中获取静态QMetaObject
实例:Subclass::staticMetaObject
- 我认为它是在Q_OBJECT
宏中自动生成的。然后FACTORY
宏创建一个静态ConcreteFactory< Subclass >
实例。其构造函数中的ConcreteFactory尝试引用Subclass :: staticMetaObject。
我对linux(gcc)上的这个实现非常满意,直到我用Visual Studio 2008编译它。由于某种原因,AbstractFactory :: m_Map在运行时是空的,并且调试器不会在工厂构造函数中断。
所以这就是引用其他静态变量的静态变量气味的来源。
如何优化此代码以避免所有这些陷阱?
答案 0 :(得分:5)
是的,标准允许这样做。
[basic.life]
部分中有许多段落开头
在对象的生命周期之前 开始但是在存储之后 该对象将占据一直 分配或者,在生命周期之后 对象已经结束,之前已经结束 对象占用的存储空间 重用或释放,任何指针 指的是存储位置 该对象将是或可能位于 只能以有限的方式使用。
并且有一个脚注表明这特别适用于您的情况
例如,在构造非POD类类型的全局对象
之前
答案 1 :(得分:2)
简答:它应该按照您编码的方式工作。见Ben Voigt答案
长答案:
做这样的事情:
不要让编译器决定何时创建全局变量,而是通过静态方法(使用静态函数变量)创建它们。这意味着它们将在首次使用时确定性地创建(并以与创建相反的顺序销毁)。
即使一个全局在构造过程中使用另一个全局使用此方法,也保证它们将按照所需的顺序创建,因此可供另一个使用(注意循环)。
struct A
{
// Rather than an explicit global use
// a static method thus creation of the value is on first use
// and not at all if you don't want it.
static A& getGlobalA()
{
static A instance; // created on first use (destroyed on application exit)
// ^^^^^^ Note the use of static here.
return instance; // return a reference.
}
private:
A()
:b_ref(B::getGlobalB()) // Do the same for B
{} // If B has not been created it will be
// created by this call, thus guaranteeing
// it is available for use by this object
}
B& b_ref;
public:
void f() { b_ref.do_something(); }
};
int main() {
a::getGlobalA().f();
}
虽然是一句警告。
答案 2 :(得分:1)
是。所有这些都位于.data
部分,一次分配(并且它不是堆)。
换句话说:如果你能够获取其地址,那就没关系,因为它肯定不会改变。
答案 3 :(得分:1)
如果B有一个构造函数,就像A有,那么它们被调用的顺序是未定义的。所以除非你很幸运,否则你的代码将无效。但是如果B不需要任何代码来初始化它,那么你的代码就可以了。它不是实现定义的。
答案 4 :(得分:1)
啊,但静态变量“未初始化”的想法是非常错误的。它们总是被初始化,而不一定是你的初始化器。特别是,在任何其他初始化之前,所有静态变量都使用零值创建。对于类对象,成员为零。因此,上面的global_a.b_ptr将始终是一个有效指针,最初是NULL,后来是&amp; global_b。这样做的结果是未指定使用非指针,而不是未定义,特别是这个代码定义明确(在C中):
// unit 1
int a = b + 1;
// unit 2
int b = a + 1;
main ... printf("%d\n", a + b); // 3 for sure
零初始化保证与此模式一起使用:
int get_x() {
static int init;
static int x;
if(init) return x;
else { x = some_calc(); init = 1; return x; }
}
确保由于无限递归而导致的不返回,或者正确初始化的值。