C ++初始化静态变量(再次)

时间:2010-12-07 16:57:51

标签: c++ variables static initialization

如果我在不同的编译单元中有两个静态变量,则不会定义它们的初始化顺序。这一课很好学。

我遇到的问题:在初始化第一个静态变量时,已经分配了所有静态变量。换句话说:

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在运行时是空的,并且调试器不会在工厂构造函数中断。

所以这就是引用其他静态变量的静态变量气味的来源。

如何优化此代码以避免所有这些陷阱?

5 个答案:

答案 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();
}

虽然是一句警告。

  • Globals是设计不良的指示
  • 依赖于其他全局变量的全局变量是另一种代码气味(特别是在构造/破坏期间)。

答案 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; }
}

确保由于无限递归而导致的不返回,或者正确初始化的值。