静态注册是否会导致惨败

时间:2015-01-25 23:24:56

标签: c++ static initialization

我希望有些类(比如说源代码)自动将自己注册到另一个类(比如目的地)。我的第一个想法是声明一个目标类的静态实例,并在源类中使用静态字段的初始化。

对于真正的问题,它将自动在Windows GUI程序中将表示Windows的类声明为表示应用程序的单例对象,以便应用程序对象的初始化能够注册所有窗口类(在WIN32中)义)。

我可以写一个概念证明,它甚至没有警告就编译和运行(MSVC 2008,gcc 4.8.2,Clang 3.5),但我想知道它是否真的是正确的,或者只是由于静态初始化惨败而导致的未定义行为。我只在SO中找到In C++, Is it good form to write code that executes before main()?,这可能与我的问题松散相关。

短篇小说(完整的可编辑来源):

我有一个目标类说A带有内部Node类(简单链接指针列表),Node *head字段和registerName方法。

class A {
    ...
    Node * head; // should be NULL
    static A instance;
    ...

我用该C ++片段声明了A的静态实例:

A A::instance;

bool A::registerName(const char *name) {
    std::cout << "Register " << name << std::endl;
    Node *node = new Node(name, head);
    head = node;
    return true;
}

然后我通过初始化静态布尔值来注册包含静态char *名称的源类(比如说C):

const char C::name[] = "ClassC";
bool C::inited = A::getInstance().registerName(C::name);

问题是:

由于静态bool C::inited的初始化取决于静态A A::instance,它会因为静态初始化失败而导致UB,或者仍然是正确的,因为我只需要在A实例中{{{要初始化为NULL的指针(并且可以在调用任何构造函数之前进行初始化)?

TL / DR:完整来源:

目的地类:

head

实施

class A
{
    class Node {
        const char *name;
        Node *next;
    public:
        Node(const char *name = 0, Node *next = 0): name(name),
            next(next) {};
        const char* getName() const { return name; }
        Node * getNext() const { return next; }

    };

    Node *head;

    static A instance;

    A() {};
    ~A();

public:
    static A& getInstance() { return instance; }

    Node *getHead() const { return head; }

    bool registerName(const char *name);
    friend int main();
};

目的地类(3个不同的文件):

#include "A.h"
#include <iostream>

A::~A(void)
{
    Node *next;
    while (head != 0) {
        next = head ->getNext();
        std::cout << "Delete Node for " << head->getName() << std::endl;
        delete head;
        head = next;
    }
}

A A::instance;

bool A::registerName(const char *name) {
    std::cout << "Register " << name << std::endl;
    Node *node = new Node(name, head);
    head = node;
    return true;
}

//

#include "A.h"

class B
{
protected:
    static bool inited;
    static const char name[];

    B() {};
    virtual ~B() {};
};

//

#include "B.h"

class C :
    public B
{
protected:
    static bool inited;
    static const char name[];

public:
    C() {};
    virtual ~C() {};
};

实施(2个档案):

#include "B.h"

class D :
    public B
{
protected:
    static bool inited;
    static const char name[];

public:
    D() {};
    virtual ~D() {};
};

//

#include "C.h"

const char C::name[] = "ClassC";
bool C::inited = A::getInstance().registerName(C::name);

主档案:

#include "D.h"

const char D::name[] = "ClassD";
bool D::inited = A::getInstance().registerName(D::name);

输出:

#include <iostream>
#include "A.h"

int main()
{
    A& instance = A::getInstance();
    A::Node* node = instance.getHead();
    while (node != NULL) {
        std::cout << node->getName() << std::endl;
        node = node->getNext();
    }
    return 0;
}

2 个答案:

答案 0 :(得分:1)

为了防止由于初始化顺序引起的问题,在函数内部实现单例作为静态变量。请参阅http://www.parashift.com/c++-faq-lite/construct-on-first-use-v2.html

在您的情况下,使用单例来注册窗口类是令人惊讶的。对于可维护的代码来说绝不是件好事。也许最好手动注册应用程序类中的所有窗口类,例如通过硬编码的初始化函数表。事件循环不应该在Application的构造函数内部运行,因为这会阻止扩展,而是例如在申请方法中。

答案 1 :(得分:1)

这个答案主要是对马库斯的一个实施例的补充。

对于静态A instance,我使用:

  • 用于定义实例的静态函数(来自Markus的答案):

    static A& getInstance() {
        static A _instance;
    
        return _instance;
    }
    
  • A类中的静态引用:A& instance(用作类中的缓存),在实现文件中定义:

    A& A::instance = A::getInitInstance();
    

对于源类的注册,我设法将所有注册逻辑放入模板基类中:

template<typename T> // T will be the actual subclass
class Base {
protected:
    static bool registerSelf() {
        A::registerClass(T::name);
        return true;
    }
    Base() {
        if (! inited) { // force an access to inited
            registerSelf(); // never executed in my tests
        }
    }
    static const bool inited;
};
template<typename T> // in the .h as it is a template
const bool Base<T>::inited = Base<T>::registerSelf();

派生类只需定义static const char * name,但(至少在MSVC 2008中)它必须是公共的,或Base<T>::registerSelf();必须明确声明为朋友(无论如何,公开它是有意义的):

class C: public Base<C> {
public:
    static const char name[];

    // friend bool Base<C>::registerSelf(); if name is not public
};

const char C::name[] = "foo"; // in implementation file 

这样,所有在程序中任何地方实际使用的子类都被注册,没有任何静态初始化失败的风险(感谢Markus),并且在我的测试中,如果声明了一个类但未使用它未注册